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. 创建监听fd
int fd = socket(PF_INET, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket");
return -1;
}
// 2. 绑定监听fd
struct 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;由于大小端没区别,因此无需htonl
int 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); // 直接传入信息数组中空闲元素的addr
pinfo->fd = cfd; // 传递cfd
if (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. 创建通信fd
int 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.11
int 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.c
clean:
rm -f server client