lv7 嵌入式开发-网络编程开发 08 TCP并发功能

目录

[1 TCP 多进程并发](#1 TCP 多进程并发)

[1.1 现象:](#1.1 现象:)

[1.2 多进程并发](#1.2 多进程并发)

[2 僵尸进程处理](#2 僵尸进程处理)

[3 TCP并发多线程](#3 TCP并发多线程)

[4 练习](#4 练习)


1 TCP 多进程并发

1.1 现象:

之前的代码,先关服务端,再次打开会出现错误bind:Address already in use

使用setsockopt 地址快速重用可解决(后续会讲套接字设置)

cpp 复制代码
#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>

#define BACKLOG 5
void ClinetHandle(int newfd);
int main(int argc, char *argv[])
{
	int fd, newfd;
	struct sockaddr_in addr, clint_addr;
	socklen_t addrlen = sizeof(clint_addr);
	
	pid_t pid;
	
	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) );
		if( (pid = fork() ) < 0){
			perror("fork");
			exit(0);
		}else if(pid == 0){
			close(fd);
			ClinetHandle(newfd);
			exit(0);
		}
		else
			close(newfd);
	}
	close(fd);
	return 0;
}
void ClinetHandle(int newfd){
	int ret;
	char buf[BUFSIZ] = {};
	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);
	}
	close(newfd);
}

原因:

虽然程序关闭,但是系统认为服务还在,所以会出现这种情况。

1.2 多进程并发

复习fork函数,wait阻塞,会使得子进程结束,父进程才结束,这样两个printf都会打印。

重点要fork()之后的代码,都会执行两遍,一遍是子进程,一遍是父进程。

cpp 复制代码
#include <stdio.h>
#include <wait.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	pid_t pid = fork();
	if(pid < 0){
		perror("fork");
		exit(0);
	}else if(pid == 0){
		printf("This is child process.\n");
	}else{
		printf("This is father process.\n");
		wait(NULL);
	}
	return 0;
}

多进程并发服务端实现:

注意子进程和父进程中的处理细节,防止子进程产生孙进程,防止父、子进程未关闭占用的资源。

另外启用了accept中两个原来参数,使用函数进行转换

cpp 复制代码
char * inet_ntoa(struct in_addr in);
cpp 复制代码
#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>

#define BACKLOG 5
void ClinetHandle(int newfd);
int main(int argc, char *argv[])
{
	int fd, newfd;
	struct sockaddr_in addr, clint_addr;
	socklen_t addrlen = sizeof(clint_addr);
	
	pid_t pid;
	
	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) );
		if( (pid = fork() ) < 0){
			perror("fork");
			exit(0);
		}else if(pid == 0){
			close(fd);     //子进程需要关闭fd,对子进程来讲已经不适用fd了,占用了资源
			ClinetHandle(newfd);
			exit(0);       //退出子进程,防止后面生成孙进程,也进入了accept等待
		}
		else
			close(newfd);   //父进程关闭newfd,因为newfd被子进程占用了
	}
	close(fd);
	return 0;
}

void ClinetHandle(int newfd){
	int ret;
	char buf[BUFSIZ] = {};
	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);
	}
	close(newfd);
}

实验效果:

2 僵尸进程处理

现象:如果客户端退出,会产生僵尸进程

解决方法:使用信号的方式解决僵尸进程,注意flags设置为SA_RESTART的意义

cpp 复制代码
#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 ClinetHandle(int newfd);
int main(int argc, char *argv[])
{
	int fd, newfd;
	struct sockaddr_in addr, clint_addr;
	socklen_t addrlen = sizeof(clint_addr);

#if 0
	struct sigaction act;
	act.sa_handler = SigHandle;
	act.sa_flags = SA_RESTART;  //如果flag = 0会退出,那么让被终止的进程继续运行。注意实验
	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);
	}

	/*创建套接字*/
	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) );
		if( (pid = fork() ) < 0){
			perror("fork");
			exit(0);
		}else if(pid == 0){
			close(fd);
			ClinetHandle(newfd);
			exit(0);
		}
		else
			close(newfd);
	}
	close(fd);
	return 0;
}
void ClinetHandle(int newfd){
	int ret;
	char buf[BUFSIZ] = {};
	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);
	}
	close(newfd);
}

3 TCP并发多线程

目的:多线程占用的资源会更少

复习:

pthread_detach() 函数用于将指定的线程设置为分离模式。分离模式的线程在退出时会自动释放资源,不需要通过 pthread_join() 来等待线程结束并获取返回值。

函数原型为:

cpp 复制代码
int pthread_detach(pthread_t thread);

参数 thread 是要设置为分离模式的线程标识符。

返回值:

  • 成功时,返回 0。
  • 失败时,返回一个非零的错误码。

注意事项:

  • 必须在线程执行之前或者在其它线程中调用 pthread_detach() 函数,否则行为未定义。
  • 一旦线程被设置为分离模式,就无法再使用 pthread_join() 来等待线程结束。
  • 分离模式的线程会在退出时自动释放其资源,但必须确保线程在退出前不会产生资源泄漏。
  • 默认情况下,线程是非分离模式,需要显式调用 pthread_detach()pthread_attr_setdetachstate() 函数将其设置为分离模式。

示例用法:

cpp 复制代码
#include <pthread.h>

void* thread_func(void* arg) {
    // 线程执行的代码
    return NULL;
}

int main() {
    pthread_t tid;
    // 创建线程

    if (pthread_create(&tid, NULL, thread_func, NULL) != 0) {
        // 处理创建线程失败的情况
        return -1;
    }

    // 设置线程为分离模式
    if (pthread_detach(tid) != 0) {
        // 处理设置线程分离模式失败的情况
        return -1;
    }

    // 继续执行其他操作
    // ...

    return 0;
}
cpp 复制代码
#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;
}

makefile也需要修改

cpp 复制代码
CC=gcc
CFLAGS=-Wall
all:client server

server:server.c
	$(CC) $^ -Wall -o $@ -lpthread

clean:
	rm client server

4 练习

使用多线程实现TCP并发代码,并使用Makefile进行编译。提交代码和完成通信的截图

tcp_server.c

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

#define CLIENT_MAX_NUM 5

void * ClientHandle(void *arg);

int main(int argc, char * argv[])
{
	int sockfd, clientfd;
	struct sockaddr_in server_addr,client_addr;
	pthread_t tid;
	socklen_t addrlen = sizeof(client_addr);

	if( argc < 3)
	{
		printf("%s <ip> <port>\n",argv[0]);
		return 0;
	}

	//1 创建socket
	sockfd = socket(AF_INET, SOCK_STREAM,0);
	if(sockfd == -1)
	{
		perror("socket");
		return 0;
	}

	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons( atoi(argv[2]) ) ;
	if( inet_aton(argv[1], &server_addr.sin_addr) == 0)
	{
		printf("Invalid address:%s\n",argv[1]);
		return 0;
	}
	
	/*地址快速重用*/
	int flag = 1, len = sizeof(int);
	if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag,len) == -1)
	{
		perror("setsockopt");
		return 0;
	}
	
	//2 绑定bind
	if(bind(sockfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
	{
		perror("bind");
		return 0;
	}

	//3 监听
	if(listen(sockfd, CLIENT_MAX_NUM) == -1)
	{
		perror("listen");
		return 0;
	}

	while(1)
	{
		//4 等待连接
		clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen); 
		
		if( clientfd == -1)
		{
			perror("accept");
			return 0;
		}
		printf("addr:%s port:%d\n",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
		pthread_create(&tid, NULL, ClientHandle, &clientfd);
		pthread_detach(tid);  //线程属性设置为分离模式

	}
	close(sockfd);
	return 0;
}



void * ClientHandle(void *arg)
{
	int ret;
	char buf[BUFSIZ] = {};
	int clientfd = *(int *)arg;

	while(1)
	{
		//bzero(buf, BUFSIZ);
		memset(buf, 0, BUFSIZ);
		ret = read(clientfd, 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(clientfd);
	return NULL;

}

tcp_client.c

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define CLIENT_MAX_NUM 5

int main(int argc, char * argv[])
{
	int clientfd;
	struct sockaddr_in server_addr;
	char buf[BUFSIZ];

	if( argc < 3)
	{
		printf("%s <ip> <port>\n",argv[0]);
		return 0;
	}

	clientfd = socket(AF_INET, SOCK_STREAM,0);
	if(clientfd == -1)
	{
		perror("socket");
		return 0;
	}

	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons( atoi(argv[2]) ) ;
	if( inet_aton(argv[1], &server_addr.sin_addr) == 0)
	{
		printf("Invalid address:%s\n",argv[1]);
		return 0;
	}

	if(connect(clientfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
	{
		perror("connect");
		return 0;
	}


	while(1)
	{
		printf(">");
		fgets(buf, BUFSIZ, stdin);
		write(clientfd, buf, strlen(buf));
	}
	close(clientfd);
	return 0;
}

makefile

cpp 复制代码
CC=gcc
CFLAGS=-Wall
all:tcp_client tcp_server
tcp_server:tcp_server.c
	$(CC) tcp_server.c -Wall -o tcp_server -lpthread

clean:
	rm tcp_server tcp_client
相关推荐
雨中rain13 分钟前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++
ProcessOn官方账号17 分钟前
如何绘制网络拓扑图?附详细分类解说和用户案例!
网络·职场和发展·流程图·拓扑学
Bessssss33 分钟前
centos日志管理,xiao整理
linux·运维·centos
s_yellowfish33 分钟前
Linux服务器pm2 运行chatgpt-on-wechat,搭建微信群ai机器人
linux·服务器·chatgpt
豆是浪个35 分钟前
Linux(Centos 7.6)yum源配置
linux·运维·centos
vvw&36 分钟前
如何在 Ubuntu 22.04 上安装 Ansible 教程
linux·运维·服务器·ubuntu·开源·ansible·devops
我一定会有钱38 分钟前
【linux】NFS实验
linux·服务器
Ven%41 分钟前
如何在防火墙上指定ip访问服务器上任何端口呢
linux·服务器·网络·深度学习·tcp/ip
是阿建吖!1 小时前
【Linux】基础IO(磁盘文件)
linux·服务器·数据库
张暮笛1 小时前
蓝牙协议——音量控制
linux