学习socket编程前,我们先来提升一下认知:
地址由IP地址、端口号构成
IP地址转换API(IP地址是字符串要转换成网络能识别格式)
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int inet_aton(const char *cp, struct in_addr *inp); //字符串转网络字节序 注意第二个参数是结构体 char *inet_ntoa(struct in_addr in); //网络字节序转字符串
字节序转换API (端口号: 主机字节序转换成网络字节序)
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); //将无符号整数hostlong从主机字节顺序转换为网络字节顺序 uint16_t htons(uint16_t hostshort); //将无符号短整数hostshort从主机字节顺序转换为网络字节顺序 uint32_t ntohl(uint32_t netlong); //将无符号整数netlong从网络字节顺序转换为主机字节顺序 uint16_t ntohs(uint16_t netshort); //将无符号短整数netshort从网络字节顺序转换为主机字节顺序
h --> host(主机) n --> net(网络) s --> short(小端) l --> long(大端)
步骤分为客户端和服务器端,详细步骤如下图示:
作用:获取套接字
//#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol)
功能:绑定IP号和端口号到socketfd
//#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
struct sockaddr { sa_family_t sa_family; //协议族 char sa_data[14]; //IP地址+端口号 };
常用struct sockaddr_in类型强转替换struct sockaddr
struct sockaddr_in { __kernel_sa_family_t sin_family; //协议族 __be16 sin_port; //端口号 struct in_addr sin_addr; //IP地址结构体 /* 填充 只为了跟sockaddr结构体对齐才能相互转换 */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; }; struct in_addr { __be32 s_addr; };
cd /usr/include/
grep "struct sockaddr_in {" * -nir
vi linux/in.h +184
功能:设置最大连接数,一直监听是否有其他的客户进程请求连接,响应连接TCP三次握手
//#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog);
功能:与客户端建立连接,保存客户端套接字对应的“地方”(包括客户端IP和端口信息等),如果已完成请求的队列为空将阻塞到下一个请求到来
//#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
收发函数有很多套可以使用:
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);
#include <sys/types.h> #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t send(int sockfd, const void *buf, size_t len, int flags);
使用与read、write类似, flags控制选项常设置为0
#include <sys/uio.h> ssize_t readv(int fd, const struct iovec *iov, int iovcnt); ssize_t writev(int fd, const struct iovec *iov, int iovcnt); struct iovec { void *iov_base; /*指向一个缓冲区,这个缓冲区是存放readv()所接收的数据或 //writev()将要发送的数据*/ size_t iov_len; /*接收的最大长度以及实际写入的长度*/ };
iovcnt 为iov大小
#include <sys/types.h> #include <sys/socket.h> ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); struct msghdr { void *msg_name; /* optional address */ socklen_t msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array */ size_t msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data, see below */ size_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags on received message */ };
#include <sys/types.h> #include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
1、2、3套多用于TCP
4、5套多用于UDP
功能:向服务端发出连接请求并进行连接
//#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
socket编程实现多方收发信息
#include <stdio.h> #include <sys/socket.h> #include <errno.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <string.h> int main() { int s_fd = 0; int ret = 0; int c_fd = 0; int n_read; int n_write; char w_buf[128]; char r_buf[128]; struct sockaddr_in addr; struct in_addr sin_addr; s_fd = socket(AF_INET, SOCK_STREAM, 0); if (s_fd == -1){ perror("socket "); exit(-1); } addr.sin_family = AF_INET; addr.sin_port = htons(8888); inet_aton("192.168.1.8", &addr.sin_addr); ret = bind(s_fd, (const struct sockaddr*)&addr, sizeof(addr)); if (ret == -1){ perror("bind "); exit(-1); } listen(s_fd, 10); int addrlen = 0; struct sockaddr_in s_addr; addrlen = sizeof(s_addr); while(1){ c_fd = accept(s_fd, (struct sockaddr*)&s_addr, &addrlen); if (c_fd == -1){ perror("accept "); exit(-1); } printf("get connect IP: %s\n", inet_ntoa(s_addr.sin_addr)); if (fork() == 0){ write(c_fd, "welcome to server!", strlen("welcome to server!")); if (fork() == 0){ while(1){ memset(r_buf, 0, 128); n_read = read(c_fd, r_buf, sizeof(r_buf)); if (n_read == -1){ perror("read "); } else if (n_read == 0){ printf("IP :%s clinet quit!\n", inet_ntoa(s_addr.sin_addr)); exit(0); } else { printf("IP: %s %s\n", inet_ntoa(s_addr.sin_addr), r_buf); } } } else { while(1){ gets(w_buf); n_write = write(c_fd, w_buf, sizeof(w_buf)); if (n_write == -1){ perror("write "); } } } } } return 0; }
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <unistd.h> int main(int argc, char **argv) { int s_fd = 0; int ret = 0; char r_buf[128]; char w_buf[128]; int n_read; int n_write; struct sockaddr_in addr; if (argc != 3){ printf("input false!\n"); exit(-1); } s_fd = socket(AF_INET, SOCK_STREAM, 0); if (s_fd == -1){ perror("socket "); exit(-1); } addr.sin_family = AF_INET; addr.sin_port = htons(atoi(argv[2])); inet_aton(argv[1], &addr.sin_addr); ret = connect(s_fd, (const struct sockaddr*)&addr, sizeof(addr)); if (ret == -1){ perror("connect "); exit(-1); } write(s_fd, "hi server!", strlen("hi server!")); if (fork() == 0){ while(1){ memset(r_buf, 0, 128); n_read = read(s_fd, r_buf, sizeof(r_buf)); if (n_read == 0){ printf("server quit!\n"); exit(0); } else if (n_read == -1){ perror("read "); } else{ printf("get message: %s\n", r_buf); } } } else { while(1){ scanf("%s", w_buf); n_write = write(s_fd, w_buf, sizeof(w_buf)); if (n_write == -1){ perror("write "); } } } return 0; }
widow命令行telnet
telnet 192.168.1.8 8888