prerequisite knowledge:
Basic TCP Server & Client: URL
Server
#include <stdio.h>
#include <string.h>
#include <unistd.h> // read and write (TCP); sendto and recvfrom (UDP)
#include <arpa/inet.h> // 包含#include <sys/socket.h>
#include <pthread.h>// 想要将两份参数通过一个位置传递给working 得定义一个信息结构体
struct SockInfo {struct sockaddr_in addr;int fd;
};// 由于有多个客户端 所以有多份信息 搞个数组存一下
struct SockInfo infos[512]; // 意味着我们最多只能和512个客户端*同时*通信
// 注意线程池是多个工作线程组成的队列,与此处不同/* infos需要上锁吗?不需要,每个子线程都使用了数组的一个位置,即多个线程没有操控同一内存空间*/void* working(void* arg) {struct SockInfo* pinfo = (struct SockInfo*)arg;printf("client socket %d, Address: %s:%d\n", pinfo->fd, inet_ntoa(pinfo->addr.sin_addr), ntohs(pinfo->addr.sin_port));// 5. 通信while (1) {char buf[1024];int len = recv(pinfo->fd, buf, sizeof(buf), 0);if (len > 0) {printf("client say: %s\n", buf);send(pinfo->fd, buf, len, 0); // 长度指定为len 不要传多了} else if (len == 0) {printf("客户端已经断开连接...\n");break;} else if (len == -1) {perror("recv");break;}} // 跳出后说明通信结束pinfo->fd = -1; // 回收close(pinfo->fd);return NULL;
}int main(int argc, char* argv[]) {// 1. 创建监听fdint fd = socket(PF_INET, SOCK_STREAM, 0);if (fd == -1) {perror("socket");return -1;}// 2. 绑定监听fdstruct sockaddr_in saddr;memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET;saddr.sin_port = htons(9999);saddr.sin_addr.s_addr = INADDR_ANY; // 宏INADDR_ANY(可以绑定本地)实际值是0=0.0.0.0;由于大小端没区别,因此无需htonlint ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));if (ret == -1) {perror("bind");return -1;}// 3. 设置监听ret = listen(fd, SOMAXCONN); // #define SOMAXCONN 128 // 最大监听队列长度 内部定义过来if (ret == -1) {perror("listen");return -1;}// 初始化信息数组int max = sizeof(infos) / sizeof(infos[0]);for (int i=0; i<max; ++i) {memset(&infos[i], 0, sizeof(infos[i]));infos[i].fd = -1; // -1表示该数组元素未被占用}// 4. 阻塞并等待客户端连接// struct sockaddr_in caddr; // 这个就不需要了// memset(&caddr, 0, sizeof(caddr));socklen_t caddr_len = sizeof(struct sockaddr_in);// 主线程不断接收:while (1) {// 创建子线程pthread_t tid;struct SockInfo* pinfo;for (int i=0; i<max; ++i) {if (infos[i].fd == -1) {pinfo = &infos[i];break;}}int cfd = accept(fd, (struct sockaddr*)&pinfo->addr, &caddr_len); // 直接传入信息数组中空闲元素的addrpinfo->fd = cfd; // 传递cfdif (cfd == -1) {perror("accept");break;}pthread_create(&tid, NULL, working, pinfo);// 此处不能使用主线程回收子线程资源 因为pthread_join是阻塞函数 与我们设想的不断accept矛盾pthread_detach(tid);}close(fd);return 0;
}
Client
#include <stdio.h>
#include <string.h>
#include <unistd.h> // read and write (TCP); sendto and recvfrom (UDP)
#include <arpa/inet.h> // 包含#include <sys/socket.h>int main(int argc, char* argv[]) {// 1. 创建通信fdint fd = socket(PF_INET, SOCK_STREAM, 0); // AF_*和PF_*值完全相同,通常混用if (fd == -1) {perror("socket");return -1;}// 2. 连接服务器struct sockaddr_in saddr;memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET;saddr.sin_port = htons(9999);inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr);saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 或者直接指定ip: 172.31.78.11int ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));if (ret == -1) {perror("connect");return -1;}printf("socket connect successful!\n");// 3. 通信int number = 0;while (1) {char buf[1024];sprintf(buf, "hello, message number #%d...\n", number++); // sprintf将数据写入字符串 而非输出到标准输出流send(fd, buf, strlen(buf)+1, 0); // 注意这里不要发送sizeof(buf),发送实际字符数+'\0'memset(buf, 0, sizeof(buf)); // 有必要清空buf的int len = recv(fd, buf, sizeof(buf), 0);if (len > 0) {printf("server say: %s\n", buf);} else if (len == 0) {printf("服务器已经断开连接...\n");break;} else if (len == -1) {perror("recv");break;}sleep(1); // 让客户端1秒发一条} // 跳出后说明通信结束close(fd);return 0;
}
makefile
server:gcc -o server server.c -lpthread &gcc -o client client.cclean:rm -f server client