2024/3/17 TCP的进程和线程通信,模拟面试总结

#include<myhead.h>
#define SER_PORT 8888//服务器端口号
#define SER_IP "192.168.65.130"//服务器IP
//定义向线程体提供参数的结构体
struct BufInfo
{
	int newfd;
	struct sockaddr_in cin;
};
//定义线程体函数
void *deal_cli_msg(void *arg)
{
	//接受传过来的消息
	int newfd = ((struct BufInfo*)arg)->newfd;
	struct sockaddr_in cin = ((struct BufInfo*)arg)->cin;

	//收发数据
	char sbuf[128] = "";
	while(1)
	{
		//将容器清空
		bzero(sbuf,sizeof(sbuf));
		int res = recv(newfd,sbuf,sizeof(sbuf)-1,0);
		if(res == 0)
		{
			printf("客户端已经下线\n");
			break;
		}

		printf("[%s  %d]:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),sbuf);
		strcat(sbuf,">_<");

		send(newfd,sbuf,strlen(sbuf),0);
		printf("发送成功\n");
	}
	//关闭与客户端通信的套接字
	close(newfd);

	//退出线程
	pthread_exit(NULL);
}

/************主程序***************/
int main(int argc, char *argv[])
{
	//创建一个套接字
	int sfd = -1;
	sfd = socket(AF_INET,SOCK_STREAM,0);
	if(sfd == -1)
	{
		perror("socket error\n");
		return -1;
	}
	printf("%d success sfd = %d\n",__LINE__,sfd);
	//设置端口号快速重用
	int reuse = 1;
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &reuse, sizeof(reuse)) ==-1)
	{
		perror("setsockopt error");
		return -1;
	}
	printf("端口号快速重用成功\n");
	//填充地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SER_PORT);
	sin.sin_addr.s_addr = inet_addr(SER_IP);
	//绑定IP地址和端口号
	if(bind(sfd,(struct sockaddr *)&sin,sizeof(sin))==-1)
	{
		perror("bind error\n");
		return -1;
	}
	printf("%d 绑定成功\n",__LINE__);
	//开启被动监听状态
	if(listen(sfd,128)==-1)
	{
		perror("listen error\n");
		return -1;
	}
	printf("%d 开启被动监听状态成功\n",__LINE__);
	//阻塞等待客户端的连接请求
	int newfd = -1;
	struct sockaddr_in cin;
	socklen_t adderlen = sizeof(cin);
	pthread_t tid = -1;
	while(1)
	{
		if((newfd=accept(sfd,(struct sockaddr *)&cin,&adderlen))==-1)
		{
			perror("accept error\n");
			return -1;
		}
		printf("[%s  %d]:发来连接请求 newfd=%d\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);

		struct BufInfo buf;
		buf.newfd = newfd;
		buf.cin = cin;
		//创建子线程用于处理通信客户端
		if(pthread_create(&tid,NULL,deal_cli_msg,&buf) != 0)
		{
			printf("pthread_create error");
			return -1;
		}
		//回收分支线程的资源
		pthread_detach(tid);
	}

	close(sfd);
	return 0;
}
#include<myhead.h>
#define SER_PORT 8888//服务器端口号
#define SER_IP "192.168.65.129"//服务器IP
//定义一个信号处理函数
void handler(int signo)
{
	if(signo == SIGCHLD)
	{
		while(1)
		{
			if(waitpid(-1,NULL,WNOHANG)==0)
			{
				break;
			}
		}
	}
}

//************主程序**************
int main(int argc, char *argv[])
{
	//调用信号处理函数
	if(signal(SIGCHLD,handler) == SIG_ERR)
	{
		perror("signal error");
		return -1;
	}
	//创建一个套接字
	int sfd = -1;
	sfd = socket(AF_INET,SOCK_STREAM,0);
	if(sfd==-1)
	{
		perror("socket error\n");
		return -1;
	}
	printf("套接字创建成功\n");
	printf("%d sfd = %d\n",__LINE__,sfd);//最小未分配原则

	//端口号快速重用
	int reuse = 1;
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &reuse, sizeof(reuse)) ==-1)
	{
		perror("setsockopt error");
		return -1;
	}
	printf("端口号快速重用成功\n");

	//填充地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SER_PORT);
	sin.sin_addr.s_addr = inet_addr(SER_IP);
	//绑定IP地址和端口号
	if(bind(sfd,(struct sockaddr *)&sin,sizeof(sin))==-1)
	{
		perror("bind error\n");
		return -1;
	}
	printf("%d 绑定成功\n",__LINE__);

	//开启被动监听状态
	if(listen(sfd,128)==-1)
	{
		perror("listen error\n");
		return -1;
	}
	printf("被动监听状态开启成功\n");

	//阻塞等待客户端的连接请求
	int newfd = -1;
	struct sockaddr_in cin;
	socklen_t addrlen = sizeof(cin);

	//定义进程号变量
	pid_t pid = -1;

	while(1)
	{	   
		if((newfd = accept(sfd,(struct sockaddr *)&cin,&addrlen))==-1)
		{
			perror("accept error\n");
			return -1;
		}
		printf("[%s  %d]:发来连接请求 newfd=%d\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);
		//创建子进程
		pid = fork();
		if(pid > 0)
		{
			close(newfd);        
		}
		else if(pid == 0)
		{
			close(sfd);
			//收发数据
			char rbuf[128]="";
			while(1)
			{
				//将容器清空
				bzero(rbuf,sizeof(rbuf));
				//向套接字中读取数据
				int res = recv(newfd,rbuf,sizeof(rbuf)-1,0);
				if(res == 0)
				{
					printf("客户端已下线\n");
					break;
				}
				printf("[%s  %d]:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),rbuf);
				//加个笑脸还回去
				strcat(rbuf,"*_*");
				send(newfd,rbuf,strlen(rbuf),0);
				printf("发送成功\n");
			}
			//关闭服务器通信的套接字
			close(newfd);
            //退出子进程
			exit(EXIT_SUCCESS);
		}
	}
		//关闭服务器
		close(sfd);
		return 0;
	}

模拟面试总结:

什么是IP地址?

IP地址是主机在网络中的唯一标识(也是路由器选择的唯一标识),当主机从一个网络切换到另一个网络时,他会更改IP地址。

IP地址也分为IPv4和IPv6,IP地址一般由网络号和主机号组成。

IP地址和MAC地址的区别?

IP地址不一定是计算机的唯一标识,随着网络的切换,IP地址会发生更改,但是MAC地址不会发生更改。

MAC地址是计算机厂商给的唯一标识,全球唯一。

什么是端口号?

端口号是计算机用来区分同一主机上的多个进程而引入的,是一个两字节的无符号整数,也是网络通信中两个决定性因素之一。同时端口号分为三类:系统指定端口号,用户可用端口号,自动分配端口号。

TCP通信中的服务器实现流程?

通过socket创建一个套接字用于后续的通信,再通过bind函数来绑定服务器的IP地址和端口号并通过listen函数开启监听效果,之后通过accept函数实现与客户端的连接,后续便可以使用recv/send函数实现与客户端的数据交换过程,完成交换数据后可通过close函数关闭套接字。

TCP通信中的客户端实现流程 ?

通过socket创建一个套接字用于后续的通信,再通过bind函数来绑定客户端的IP地址和端口号并通过listen函数开启监听效果,之后通过connect函数实现与服务器的连接,后续便可以使用recv/send函数实现数据交换过程,完成交换数据后可通过close函数关闭套接字。

TCP和UDP的区别?

TCP ----> 稳定

1> 提供面向连接的,可靠的数据传输服务

2> 传输过程中,数据无误、数据无丢失、数据无失序、数据无重复

1、TCP会给每个数据包编上编号,该编号称之为序列号

2、每个序列号都需要应答包应答,如果没有应答,则会将上面的包重复发送直到正确为止

3> 数据传输效率低,耗费资源多

4> 数据收发是不同步的

1、为了提高效率,TCP会将多个较小,并且发送间隔短的数据包,沾成一个包发送,该现象称为沾包现象

2、该沾包算法称之为Nagle算法

5> TCP的使用场景:对传输质量比较高的以及传输大量数据的通信,在需要可靠通信的传输场合,一般使用TCP协议

例如:账户登录,大型文件下载的时候

UDP ----> 快速

1> 面向无连接的,不保证数据可靠的,尽最大努力传输的协议

2> 数据传输过程中,可能出现数据丢失、重复、失序现象

3> 数据传输效率高,实时性高

4> 限制每次传输的数据大小,多出部分直接忽略删除

5> 收发是同步的,不会沾包

6> 适用场景:发送小尺寸的,在接收到数据给出应答比较困难的情况下

例如:广播、通讯软件的音视频

TCP通信中的三次握手四次挥手?

UDP中是否可以使用connect函数进行连接?

1> udp通信中可以使用connect函数,他是将服务器与某个客户端建立一个唯一通道

2> 好处:传输效率高,稳定性高,数据干扰较小

3> 在服务器端使用connect与某个特定的客户端建立连接后,服务器就不再接收其他客户端的消息了

4> 如果想要断开,需要再使用一次connect函数,但是需要将地址信息结构体中的sin_addr改成AF_UNSPEC

5> 在udp中可以多次使用connect函数与其他客户端建立连接,但是在TCP中只能进行一次连接

6> 当UDP中使用了connect与某个特定的客户端建立连接后,就可以正常使用read/write、send/recv函数完成通信
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/PC12581/article/details/136768190