在实例中,经常会存在多个客户端向服务端发送连接请求和传递数据的情况,为了解决这个问题,我们需要利用多进程或者多线程来实现并发。
TCP多进程并发
要想实现TCP多进程并发,就要从accept函数下手,每次接收到来自客户端的请求,都生成一个新的文件描述符,记录下客户端的地址信息(IP和端口号),然后通过新文件描述符用read函数读取传送的内容。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接
- 参数:
- sockfd : 用于监听的文件描述符
- addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port)
- addrlen : 指定第二个参数的对应的内存大小
- 返回值:
- 成功 :用于通信的文件描述符
- -1 : 失败
服务端实现代码
客户端代码与【嵌入式开发之网络编程】Socket套接字及TCP通信的实现中一致,关于进程创建和回收,参考【嵌入式开发之并发程序设计】进程的创建和回收
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <signal.h>
#include <sys/wait.h>
#define BACKLOG 5
void SigHandle(int sig) {
if (sig == SIGCHLD) {
printf("client exited\n");
wait(NULL);
}
}
void ClientHandle(int newfd);
int main(int argc, const char *argv[])
{
int fd, newfd;
struct sockaddr_in addr, clint_addr;
socklen_t addrlen = sizeof(clint_addr);
#if 1
struct sigaction act;
act.sa_handler = SigHandle;
act.sa_flags = SA_RESTART;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD, &act, NULL);
#else
signal(SIGCHLD, SigHandle);
#endif
pid_t pid;
if (argc < 3) {
fprintf(stderr, "%s<addr><port>\n", argv[0]);
exit(0);
}
/*创建套接字
* 使用IPv4互联网协议
* 流式套间字
* 协议唯一对应*/
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
exit(0);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
#if 0
addr.sin_addr.s_addr = inet_addr(argv[1]);
#else
if (inet_aton(argv[1], &addr.sin_addr) == 0) {
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
#endif
//地址快速重用
int flag = 1, len = sizeof(int);
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) {
perror("setsockopt");
exit(1);
}
//绑定通信结构体
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(0);
}
//设置套接字为监听模式
if (listen(fd, BACKLOG) == -1) {
perror("listen");
exit(0);
}
while(1) {
//接受客户端的连接请求,生成新的和客户端通信的套间字
if ((newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen)) < 0) {
perror("accept");
exit(0);
}
/*IP地址转换成点分十进制
* 端口号网络字节序转成主机字节序
* 生成子进程,并读取数据*/
printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port));
if ((pid = fork()) < 0) {
perror("fork");
exit(0);
} else if (pid == 0) {
close(fd);
ClientHandle(newfd);
exit(0);
} else {
close(newfd);
}
}
close(fd);
return 0;
}
void ClientHandle(int newfd) {
int ret;
char buf[BUFSIZ] = {};
while(1){
memset(buf, 0, BUFSIZ);
ret = read(newfd, buf, BUFSIZ);
if (ret < 0){
perror("read");
exit(0);
} else if (ret == 0) {
break;
} else {
printf("buf = %s\n", buf);
}
}
close(newfd);
}
运行结果(服务端)
$ ./server 0 8888
addr:127.0.0.1 port:54564
buf = woeoeoe
addr:127.0.0.1 port:47698
buf = woeooe
TCP多线程并发
服务端实现代码
这部分涉及到线程的创建,参考【嵌入式开发之并发程序设计】线程的基本概念、pthread线程库的创建、互斥锁的使用、条件变量以及线程池的概念和使用
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <pthread.h>
#define BACKLOG 5
void *ClinetHandle(void *arg);
int main(int argc, char *argv[])
{
int fd, newfd;
struct sockaddr_in addr, clint_addr;
pthread_t tid;
socklen_t addrlen = sizeof(clint_addr);
if(argc < 3){
fprintf(stderr, "%s<addr><port>\n", argv[0]);
exit(0);
}
/*创建套接字*/
fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0){
perror("socket");
exit(0);
}
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(argv[2]) );
if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
/*地址快速重用*/
int flag=1,len= sizeof (int);
if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) {
perror("setsockopt");
exit(1);
}
/*绑定通信结构体*/
if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
perror("bind");
exit(0);
}
/*设置套接字为监听模式*/
if(listen(fd, BACKLOG) == -1){
perror("listen");
exit(0);
}
while(1){
/*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);
if(newfd < 0){
perror("accept");
exit(0);
}
printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );
/*创建线程,并设置分离属性*/
pthread_create(&tid, NULL, ClinetHandle, &newfd);
pthread_detach(tid);
}
close(fd);
return 0;
}
void *ClinetHandle(void *arg){
int ret;
char buf[BUFSIZ] = {};
int newfd = *(int *)arg;
while(1){
//memset(buf, 0, BUFSIZ);
bzero(buf, BUFSIZ);
ret = read(newfd, buf, BUFSIZ);
if(ret < 0)
{
perror("read");
exit(0);
}
else if(ret == 0)
break;
else
printf("buf = %s\n", buf);
}
printf("client exited\n");
close(newfd);
return NULL;
}
运行结果(服务端)
$ ./server 0 8888
addr:127.0.0.1 port:59782
buf = Hello world
addr:127.0.0.1 port:34552
buf = haode
client exited
client exited