多线程TCP服务器程序
主线程负责监听有无新的连接,当新的连接到来时,就创建一个新线程服务客户(与客户端进行通信)。
void *working(void *arg)
{
int cfd = *((int *)arg);
while(1)
{
// 与客户端进行通信
}
close(cfd); // 关闭通信socket。与多进程实现不同,主线程中不用关闭cfd,因为线程之间文件描述符不共享。
}
int main()
{
// 创建socket,绑定bind,监听listen
while(1)
{
int cfd = accept(lfd, NULL, NULL);
pthread_t tid;
pthread_create(&tid, NULL, working, &cfd);
pthread_detach(tid); // 调用join会使主线程一直阻塞在这,监听不了,可以调用detach
}
close(lfd); // 新线程中不用关闭lfd,原因同上
return 0;
}
这个程序在一些情况下会出错,因为每次调用accept
都会向cfd
覆盖写入一个新值,可能出现如下情况。
- 主线程调用
accept
并将结果存入cfd
,并创建新线程1,新线程1还没来得及执行。 - 主线程又调用
accept
并将结果存入cfd
,原先的值被覆盖,又创建一个新线程2。 - 此时线程1和线程2参数中的
cfd
指向同一块数据,即同一个socket,发生错误。
改进后:
int main()
{
// 创建socket,绑定bind,监听listen
while(1)
{
int *p = (int *)malloc(sizeof(int));
*p = accept(lfd, NULL, NULL);
pthread_t tid;
pthread_create(&tid, NULL, working, p);
pthread_detach(tid); // 调用join会使主线程一直阻塞在这,监听不了,可以调用detach
}
close(lfd); // 新线程中不用关闭lfd,原因同上
return 0;
}
每次调用accept
时,我们首先调用malloc
分配一个整数变量的内存空间,用于存放有待accept
返回的已连接描述符。这使得每个线程都有各自的已连接描述符副本。(即参数中传的地址指向不同的数据)
多进程TCP服务器程序
父进程负责监听有无新的连接,当新的连接到来时,就fork
一个子进程服务客户(与客户端进行通信)。
int main()
{
// 创建socket,绑定bind,监听listen
while(1)
{
int cfd = accept(lfd, NULL, NULL);
if(cfd == -1)
{
perror("accept");
exit(1);
}
pid_t pid = fork();
if(pid == 0) // 子进程
{
close(lfd); // 关闭监听描述符
while(1)
{
// 与客户端进行通信
}
close(cfd); // 关闭通信描述符
exit(0);
}
else if(pid > 0) // 父进程
{
close(cfd); // 关闭刚创建出来的通信描述符
}
else if(pid < 0)
{
perror("fork");
exit(1);
}
}
close(lfd);
return 0;
}
代码分析:
子进程与父进程共享打开的文件(socket
也看作文件),所以父进程fork
后,监听socket
和通信socket
的引用计数均为2,当父进程或子进程结束后,对应的文件描述符的引用计数由2->1,套接字不会被清理或释放,所以需要如下操作:
- 父进程中需要
close(cfd)
关闭刚创建出来的通信描述符。 - 子进程中需要
close(lfd)
关闭监听描述符。
源码
多线程
// MultiThreadServer.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *working(void *arg)
{
int cfd = *((int *)arg);
free(arg);
while(1)
{
// 接收数据
char buf[1024];
memset(buf, 0, sizeof buf);
int len = read(cfd, buf, sizeof buf);
if(len > 0)
{
printf("客户端say: %s\n", buf);
write(cfd, buf, len);
}
else if(len == 0)
{
printf("客户端断开了连接...\n");
break;
}
else
{
perror("read");
break;
}
}
close(cfd);
}
int main()
{
const char *ip = "192.168.154.137";
// 1、创建监听的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1)
{
perror("socket");
return 1;
}
// 2、将socket()返回值和本地的IP端口绑定到一起
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(10000);
inet_pton(AF_INET, ip, &server.sin_addr.s_addr);
int ret = bind(lfd, (struct sockaddr *)&server, sizeof server);
if(ret == -1)
{
perror("bind");
return 1;
}
// 3、设置监听
ret = listen(lfd, 128);
if(ret == -1)
{
perror("listen");
return 1;
}
while(1)
{
int *p= malloc(sizeof(int)); // 使得每个线程都有自己的已连接描述符副本
*p = accept(lfd, NULL, NULL);
pthread_t tid;
pthread_create(&tid, NULL, working, p);
pthread_detach(tid);
}
close(lfd);
return 0;
}
多进程
// MultiProcessServer.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
int main()
{
const char *ip = "192.168.154.137";
// 1、创建监听的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1)
{
perror("socket");
return 1;
}
// 2、将socket()返回值和本地的IP端口绑定到一起
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(10000);
inet_pton(AF_INET, ip, &server.sin_addr.s_addr);
int ret = bind(lfd, (struct sockaddr *)&server, sizeof server);
if(ret == -1)
{
perror("bind");
return 1;
}
// 3、设置监听
ret = listen(lfd, 128);
if(ret == -1)
{
perror("listen");
return 1;
}
// 和客户端通信
while(1)
{
// 4、阻塞并接受客户端连接
int cfd = accept(lfd, NULL, NULL);
if(cfd == -1)
{
perror("accept");
return 1;
}
pid_t pid = fork();
if(pid == 0)
{
close(lfd);
while(1)
{
// 接收数据
char buf[1024];
memset(buf, 0, sizeof buf);
int len = read(cfd, buf, sizeof buf);
if(len > 0)
{
printf("客户端say: %s\n", buf);
write(cfd, buf, len);
}
else if(len == 0)
{
printf("客户端断开了连接...\n");
break;
}
else
{
perror("read");
break;
}
}
close(cfd);
exit(0);
}
else if(pid > 0)
{
close(cfd);
}
else if(pid < 0)
{
perror("fork");
exit(1);
}
}
close(lfd);
return 0;
}
客户端
// client.c
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
const char *ip = "192.168.154.137";
int main()
{
// 1、创建用于通信的套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1)
{
perror("socket");
return 1;
}
// 2、连接服务器
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
inet_pton(AF_INET, ip, &addr.sin_addr.s_addr);
int ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
if(ret == -1)
{
perror("connect");
return 1;
}
// 通信
while(1)
{
char recvbuf[1024];
fgets(recvbuf, sizeof(recvbuf), stdin);
write(fd, recvbuf, strlen(recvbuf) + 1);
read(fd, recvbuf, sizeof(recvbuf));
printf("recv buf: %s", recvbuf);
sleep(1);
}
close(fd);
return 0;
}