服务器端:
#include <netdb.h> #include <sys/socket.h> #include <time.h> #include <unistd.h> #include <memory.h> #include <signal.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <arpa/inet.h> #include <errno.h> #include <sys/types.h> #include <sys/wait.h> #include <pthread.h> #include "msg.h" int sockfd; /*信号处理函数*/ void sig_handler(int signo) { if (signo == SIGINT) { printf("server close!\n"); /*关闭socket*/ close(sockfd); exit(1); } } /*输出客户端连接的地址信息*/ void out_fd(int fd) { struct sockaddr_in addr; socklen_t len = sizeof(addr); //从fd中获得连接的客户端相关信息 if (getpeername(fd, (struct sockaddr*)&addr, &len) < 0) { perror("getpeername error"); return; } char ip[16]; memset(ip, 0, sizeof(ip)); int port = ntohs(addr.sin_port); inet_ntop(AF_INET, &addr.sin_addr.s_addr, ip, sizeof(ip)); printf("%16s(%5d) close!\n", ip, port); } //服务器发送数据给客户端 void do_service(int fd) { /*与客户端进行读写操作*/ char buff[512]; while (1) { memset(buff, 0, sizeof(buff)); size_t size; if ((size = read_msg(fd, buff, sizeof(buff))) < 0) { perror("protocal error"); break; } else if (0 == size) { break; } else { printf("%s\n", buff); if (write_msg(fd, buff, sizeof(buff)) < 0) { //假如客户端关闭 if (errno == EPIPE) { break; } printf("protocal error"); } } } } /*线程函数*/ void* th_fn(void *arg) { int fd = (int)arg; do_service(fd); out_fd(fd); close(fd); return (void*)0; } int main(int argc, char* argv[]) { /*输入端口号*/ if (argc < 2) { printf("usage: %s #port\n", argv[0]); exit(1); } /*注册SIFINT信号SIGINT,“ctrl + c”终止程序运行*/ if (signal(SIGINT, sig_handler) == SIG_ERR) { perror("signal sigint error"); exit(1); } /*1.创建socket * 注:socket创建在内核中,是一个结构体 * AF_INET:IPV4 * SOCK_STREAM:tcp协议 * */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket error"); exit(1); } /*2.将socket和地址(ip、port)进行绑定*/ struct sockaddr_in serveraddr; memset(&serveraddr, 0, sizeof(serveraddr)); //地址中填入ip、port、internet地质族类型 serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(atoi(argv[1]));//转换为网络字节序 serveraddr.sin_addr.s_addr = htons(INADDR_ANY);//响应所有的网卡请求 if (bind(sockfd, (struct sockaddr*)(&serveraddr), sizeof(serveraddr)) < 0)//注意地址要强制转换为通用地址 { perror("bind error"); exit(1); } /*3.调用listen函数启动监听(指定的端口监听) * 通知系统去接收来自客户端的连接请求 * (将接收到的客户端的请求放置到对应的队列中) *第二个参数:指定队列的长度 * */ if (listen(sockfd, 10) < 0) { perror("listen error"); exit(1); } /*4.调用accept函数从队列中获取一 * 个客户端的请求连接,并返回一个新的socket描述符 * 第二个参数:用来获取客户端的地址信息,如果不需要传入NULL * 注意:若没有客户端连接,调用此函数会阻塞。 * */ struct sockaddr_in clientaddr; socklen_t clientaddr_len = sizeof(clientaddr); /*设置线程的分离属性*/ pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); while (1) { //主控线程负责调用accept去获得客户端的连接 int fd = accept(sockfd, NULL, NULL); if (fd < 0) { perror("accept error"); continue; } /*5.启动子线程和连接的客户端进行通信 */ pthread_t th; int err; //以分离状态启动子进程 if ((err = pthread_create(&th, &attr, th_fn, (void*)fd)) != 0) { perror("pthread create error"); } //主控线程销毁线程属性 pthread_attr_destroy(&attr); } return 0; }
服务器端的程序主要将上篇博文中的服务器端程序多进程部分修改为了多线程,并且采用线程属性分离的机制,保证线程结束后能够自动回收,避免使用主控线程等待机制(pthread_join)。同时,修改了客户端地址信息打印函数,通过套接字的描述符访问客户端的地址信息。
客户端、通信协议部分代码与上一篇博文一致,无需更改。