socket多线程 , 多进程编程及UDP编程(实现一个服务器 n个客户端)

多线程服务器端的实现

在之前的文章已经详细介绍过socket网络编程 , 那么接下来让我们看看多线程的网络编程如何实现
大体的思路就是在accpet之后 , 创建一个新的线程供客户端所使用.

以下是服务器端代码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>


void *thread_fun(void *arg)
{	
	int c = (int)arg;
	
	while(1)
	{
		char buff[128] = {0};
		int n = recv(c,buff,1,0);
		if(n <= 0)
		{
			break;
		}
		printf("buff[%d]=%s\n",c,buff);
		send(c,"ok",2,0);
	}
	printf("one client over\n");
	close(c);
	//pthread_exit(NULL);	
	
}

int main()
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd != -1);

	struct sockaddr_in saddr,caddr;

	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res != -1);
	listen(sockfd,5);//

	while(1)
	{
		int len = sizeof(caddr);
		int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
		if(c < 0)
		{
			continue;
		}
		pthread_t id;
		pthread_create(&id,NULL,thread_fun,(void*)c);
		//char *s = NULL;
		//pthread_join(id,(void**)&s);
		//break;
	}

	exit(0);
}

在这个代码中 , 最开始遇到的问题就是pthread_join() [等待一个线程的结束]的使用 , 如果在主函数中加入 , 当多个客户端向服务器发起连接的时候 , 只能启动一个线程 , 其他只会完成三次连接后,进入等待队列.不能够实现多个客户端向服务器发送信息.

多线程客户端的实现

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main()
{
	int sockfd1 = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd1 != -1 );

	struct sockaddr_in saddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = connect(sockfd1,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res != -1);

	while(1)
	{
		char buff[128] = {0};
		printf("input:\n");
		fgets(buff,128,stdin);
		if(strncmp(buff,"end",3) ==0 )
		{
			break;
		}
		send(sockfd1,buff,strlen(buff),0);
		memset(buff,0,128);
		recv(sockfd1,buff,127,0);
		printf("buff=%s\n",buff);
	}
	close(sockfd1);
	return 0;
}

在这里插入图片描述

从运行结果来 , 看能够实现多线程网络编程.至于结果显示的最后一个字符为空 , 是因为在输入时加了空格 ; 而服务器端返回的ok数目与客户端发送的字符数不尽相同 ,这是由于存放消息的接收缓冲区中还存在数据(此时接收缓冲区有多少数据就会向客户端返回多少个ok(注:每两个字节一个ok));其实这些都可以忽略 , 最主要看的是多线程的实现.

多进程客户端的实现

客户端的代码与之前的相同

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main()
{
	int sockfd1 = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd1 != -1 );

	struct sockaddr_in saddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = connect(sockfd1,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res != -1);

	while(1)
	{
		char buff[128] = {0};
		printf("input:\n");
		fgets(buff,128,stdin);
		if(strncmp(buff,"end",3) ==0 )
		{
			break;
		}
		send(sockfd1,buff,strlen(buff),0);
		memset(buff,0,128);
		recv(sockfd1,buff,127,0);
		printf("buff=%s\n",buff);
	}
	close(sockfd1);
	return 0;

}

多进程服务器端的实现

多进程的实现 , 要用fork()来实现 . 同多线程一样, 也是在accept之后 , 对每个客户端创建子进程.

以下是实现代码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<signal.h>

void sig_fun(int sig)
{
	/*
	当瞬间接受多个信号  , 服务器会当作是一个进程 , 造成崩溃.
	解决方法 : 使用 waitpid()  来代替 wait()
	*/
	int val = 0;
	wait(&val);
}

int main()
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd != -1);

	struct sockaddr_in saddr,caddr;

	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res != -1);
	listen(sockfd,5);//

	//signal(SIGCHLD,sig_fun);//使用信号 只有在子进程结束之后父进程才会收到信号  父进程才会执行wait()函数
	while(1)
	{
		int len = sizeof(caddr);
		int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
		if(c < 0)
		{
			continue;
		}
		pid_t pid = fork();
		if(pid ==-1)
		{
			close(c);
			continue;
		}
		if(pid ==0)
		{
			while(1)
			{
				char buff[128] = {0};
				int n = recv(c,buff,127,0);
				if(n <= 0)
				{
					break;
				}

				printf("buff=%s\n",buff);
				send(c,"ok",2,0);
			}
			close(c);
			printf("one client over\n");
			exit(0);
		}
		//此时的c已经++ , 在 父 子 进程都可以对c(未++)的进行操作  会造成崩溃  
		//要在父进程中手动关闭
		close(c);
	}

	exit(0);
}

在代码中值得关注的就是父进程为什么要close( c )?
在没有创建子进程之前 , 套接字描述符 c 就存在 , 当子进程创建成功时 , c的值就会++(先记作大写C) ;但此时的子进程 和父进程 是都可以对c进行操作的 , 如果代码编写错误就造成程序的崩溃 ; 所以要在子进程创建成功之后 就要手动关闭 c.

如果程序这样写 , 就会出现问题 , 就是在子进程结束之后 , 并没有对子进程进行处理 , 会造成僵死进程
在这里插入图片描述如果在父进程中使用wait() , 就会使得只能一个客户端连入 ; 达不到我们所期望的那样 ; 此时就要引入信号机制 .
#include<signal.h>
signal(SIGCHLD,sig_fun);
使用信号 只有在子进程结束之后父进程才会收到信号 父进程才会执行wait()函数.
这样就能解决僵死进程的出现.

但是这样就会出现一个新的问题 , 当瞬间接受多个信号 , 服务器会当作是一个进程 , 造成崩溃.
解决方法 : 使用 waitpid() 来代替 wait() ; waitpid()会暂时停止目前进程的执行 ,直到有信号来到或子进程结束。这里就不多赘述了.

UDP服务器端的实现

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>


int main()
{
	int sockfd = socket(AF_INET,SOCK_DGRAM,0);
	assert(sockfd != -1);

	struct sockaddr_in saddr,caddr;

	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res != -1);

	while(1)
	{
		
		int len = sizeof(caddr);
		char buff[128] = {0};
		int c = recvfrom(sockfd,buff,127,0,(struct sockaddr*)&caddr,&len);
		if(c == -1)
		{
			continue;
		}
		printf("port:%d,buff=%s\n",htons(caddr.sin_port),buff);

		sendto(sockfd,"ok",2,0,(struct sockaddr*)&caddr,sizeof(caddr));
	}
	exit(0);
}

UDP客户端的实现

UDP 是User Datagram Protocol的简称 , 中文名是用户数据报协议 , 在网络中它与TCP协议一样用于处理数据包 , 是一种无连接的 , 不可靠的 协议。
UDP的实现与TCP基本相似 ,以下是UPD的编写流程.

在这里插入图片描述

头文件:#include <sys/types.h>   #include <sys/socket.h>

定义函数:int sendto(int s, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen);

函数说明: sendto() 用来将数据由指定的socket 传给对方主机.

参数说明 :
s      : 为已建好连线的socket(socket描述符) , 如果利用UDP协议则不需经过连线操作.
msg : 指向欲连线的数据内容.
flags : 一般设0, 详细描述请参考send().
to      : 用来指定欲传送的网络地址, 结构sockaddr 请参考bind().
tolen : 为sockaddr 的结果长度.

sendto()返回值: 成功则返回实际传送出去的字符数, 失败返回-1, 错误原因存于errno 中.

头文件:#include <sys/types.h>   #include <sys/socket.h>

定义函数:int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);

函数说明: recvfrom()是从指定地址接收UDP数据报。

参数说明 :
s           : socket描述符。
buf        : UDP数据报缓存地址。
len        : UDP数据报长度。
flags     :该参数一般为0。
from      : recvfrom()函数参数,struct sockaddr 类型,指明UDP数据从哪里收。
fromlen : recvfrom()函数参数,struct sockaddr_in类型,指明从哪里接收UDP数据报。

recvfrom()返回值: 对于recvfrom()函数,成功则返回接收到的字符数,失败则返回-1,错误原因存于errno中。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main()
{
	int sockfd = socket(AF_INET,SOCK_DGRAM,0);
	assert(sockfd != -1 );

	struct sockaddr_in saddr,caddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	while(1)
	{
		char buff[128] = {0};
		printf("input:\n");
		fgets(buff,128,stdin);

		sendto(sockfd,buff,strlen(buff),0,(struct sockaddr*)&saddr,sizeof(saddr));
		
		int len = sizeof(saddr);
		memset(buff,0,128);

		recvfrom(sockfd,buff,127,0,(struct sockaddr*)&saddr,&len);
		printf("buff=%s\n",buff);
	}
	close(sockfd);
}

UDP服务器端的实现

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main()
{
	int sockfd = socket(AF_INET,SOCK_DGRAM,0);
	assert(sockfd != -1);

	struct sockaddr_in saddr,caddr;

	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res != -1);

	while(1)
	{
		int len = sizeof(caddr);
		char buff[128] = {0};
		int c = recvfrom(sockfd,buff,127,0,(struct sockaddr*)&caddr,&len);
		if(c == -1)
		{
			continue;
		}
		printf("port:%d,buff=%s\n",htons(caddr.sin_port),buff);

		sendto(sockfd,"ok",2,0,(struct sockaddr*)&caddr,sizeof(caddr));
	}
	exit(0);
}

注意 : udp 是无连接的 只要你发 我就收

问题 : 在服务器端的代码中 , int c = recvfrom(sockfd,buff,127,0,(struct sockaddr*)&caddr,&len);
如果将每次接收的字符由127 , 改为1 ; 那么在客户端发送"abc" , 会显示"a";再发送"123" , 会显示什么 , 是 “b” ?还是 “1” ?

在这里插入图片描述
解释 : recvfrom 一次没读完 , 剩下的就丢失. 所以要设置的足够大 , 能够一次性全部接收

  • 2
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值