通过该简单的demo学习socket网络编程,注释会详细解释每一行的作用,并且代码可以编译运行。
server.c #include <sys/types.h> /* basic system data types */ #include <sys/socket.h> /* basic socket definitions */ #include <sys/time.h> /* timeval{} for select() */ #include <time.h> /* timespec{} for pselect() */ #include <netinet/in.h> /* sockaddr_in{} and other Internet defns */ #include <arpa/inet.h> /* inet(3) functions */ #include <errno.h> #include <fcntl.h> /* for nonblocking */ #include <netdb.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> /* for S_xxx file mode constants */ #include <sys/uio.h> /* for iovec{} and readv/writev */ #include <unistd.h> #include <sys/wait.h> #include <sys/un.h> /* for Unix domain sockets */ #include <sys/select.h> /* for convenience */ #include <sys/sysctl.h> #include <poll.h> /* for convenience */ #include <strings.h> /* for convenience */ #include <sys/ioctl.h> #include <pthread.h> size_t readn(int fd, void *buffer, size_t size) { char *buffer_pointer = buffer; int length = size; while (length > 0) { int result = read(fd, buffer_pointer, length); if (result < 0) { if (errno == EINTR) continue; /* 考虑非阻塞的情况,这里需要再次调用read */ else return (-1); } else if (result == 0) break; /* EOF(End of File)表示套接字关闭 */ length -= result; buffer_pointer += result; } return (size - length); /* 返回的是实际读取的字节数*/ } void read_data(int sockfd) { ssize_t n; char buf[1024]; int time = 0; for (;;) { fprintf(stdout, "block in read\n"); if ((n = readn(sockfd, buf, 1024)) == 0) return; time++; fprintf(stdout, "1K read for %d \n", time); // 接收设置延迟时间,观察client发送数据和server接收数据,client将数据写入到缓冲区之后就会立即返回成功 sleep(5); } } int main(int argc, char **argv) { int listenfd, connfd; socklen_t clilen; // 声明地址信息结构体 struct sockaddr_in cliaddr, servaddr; // 创建套接字 listenfd = socket(AF_INET, SOCK_STREAM, 0); // 将指定内存区域的前 n 个字节全部设置为零,用于初始化内存区域 bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; // INADDR_ANY IPv4通配地址 IN6ADDR_ANY IPv6通配地址 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置端口号,设置为0时由操作系统选择一个空闲的端口 // htons函数:将一个无符号短整型(通常是16位)的主机数值转换为网络字节顺序 servaddr.sin_port = htons(12345); // bind到本地地址,端口为12345 // bind函数套接字和地址关联 bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); // 初始化创建的套接字,可以认为是一个"主动"套接字,其目的是之后主动发起请求 // 通过 listen 函数,可以将原来的"主动"套接字转换为"被动"套接字 listen(listenfd, 1024); /* 循环处理用户请求 */ for (;;) { clilen = sizeof(cliaddr); // 客户端发起连接请求,accept函数接受请求建立连接 // 第一个参数listenfd是用于监听的套接字,返回的是新建立连接的套接字 connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen); read_data(connfd); /* 读取数据 */ close(connfd); /* 关闭连接套接字,注意不是监听套接字*/ } } client.c #include <sys/types.h> /* basic system data types */ #include <sys/socket.h> /* basic socket definitions */ #include <sys/time.h> /* timeval{} for select() */ #include <time.h> /* timespec{} for pselect() */ #include <netinet/in.h> /* sockaddr_in{} and other Internet defns */ #include <arpa/inet.h> /* inet(3) functions */ #include <errno.h> #include <fcntl.h> /* for nonblocking */ #include <netdb.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> /* for S_xxx file mode constants */ #include <sys/uio.h> /* for iovec{} and readv/writev */ #include <unistd.h> #include <sys/wait.h> #include <sys/un.h> /* for Unix domain sockets */ #include <sys/select.h> /* for convenience */ #include <sys/sysctl.h> #include <poll.h> /* for convenience */ #include <strings.h> /* for convenience */ #include <sys/ioctl.h> #include <pthread.h> #include <stdarg.h> /* ANSI C header file */ #include <syslog.h> /* for syslog() */ # define MESSAGE_SIZE 102400 void error(int status, int err, char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); if (err) fprintf(stderr, ": %s (%d)\n", strerror(err), err); if (status) exit(status); } void send_data(int sockfd) { char *query; query = malloc(MESSAGE_SIZE + 1); for (int i = 0; i < MESSAGE_SIZE; i++) { query[i] = 'a'; } query[MESSAGE_SIZE] = '\0'; const char *cp; cp = query; size_t remaining = strlen(query); while (remaining) { int n_written = send(sockfd, cp, remaining, 0); fprintf(stdout, "send into buffer %d \n", n_written); if (n_written <= 0) { error(1, errno, "send failed"); return; } remaining -= n_written; cp += n_written; } return; } int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) error(1, 0, "usage: tcpclient <IPaddress>"); sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(12345); inet_pton(AF_INET, argv[1], &servaddr.sin_addr); int connect_rt = connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); if (connect_rt < 0) { error(1, errno, "connect failed "); } send_data(sockfd); exit(0); } 对于 send 来说,返回成功仅仅表示数据写到发送缓冲区成功,并不表示对端已经成功收到。
对于 read 来说,需要循环读取数据,并且需要考虑 EOF 等异常条件。
...