实验八 进程间通信——Socket(2)

实验八 进程间通信——Socket(2)

一、实验目的

1、了解采用Socket通信的原理。
2、掌握Socket的创建及使用方法。

二、实验原理

1、通过Socket进行进程间通信的流程:服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有客户端初始化一个Socket,然后连接服务器(connect),若连接成功,则客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
2、TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址。TCP客户端依次调用socket()、connect()后将向TCP服务器发送一个连接请求。TCP服务器监听到这个请求后,调用accept()函数接收请求,连接建立成功。之后可以开始网络I/O操作,类同于管道的阻塞读写I/O操作。

三、实验内容

1、创建一个服务器端和若干个客户端。
服务器端可实现包括:接收并区分来自客户端的数据,并将用户输入的内容群发至所有在线客户端(类似qq群聊形式);此外服务器可主动向在线客户端发送数据(类似qq通知信息);并可以统计在线人数等。
客户端可实现包括:输入文字并且向服务器发送消息、接收来自服务器端的消息,接收来自另一客户端的消息(类似qq私聊形式);用户控制客户端退出。
2、在服务器端和客户端基于socket实现其通信过程。
3、 服务器与客户端的具体内容可根据实际工作情况发挥,体现原创精神即可。
需要:1)简述程序功能;2)具体的实现源代码及注释;3)实现的过程和结果截图。

功能描述

1.客户端注册名字
2.用户可群发,也可选择某一个人私聊
3.服务器对收到的信息进行群发或者发给某一个特定的用户
4.当有用户加入时,服务器主动向其发送欢迎信息,并提示其他用户有新用户加入,然后服务器进行在线人数的统计
5.当有用户退出时,服务器提示其他用户某某用户退出,同时进行在线人数的统计

截图:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

思路简述:当有新的客户端加入时,创建socket,准备自己的ip地址和端口号,并连接服务器端,然后为客户端注册一个昵称,新建一个线程为该客户端服务,调用read函数随时接收服务器端发送来的内容。当客户需要发送消息时,调用write函数,当群发时只需要把内容发给服务器即可,需要私聊是需要输入对方的昵称和发送内容,然后发送给服务器端,用户端可以自行选择退出。
服务器端创建一个Client类型的结构体,成员变量有套接字描述符,用户昵称和套接字地址结构,然后新建Client数组,创建一个socket,使用bind进行绑定,然后使用listen监听连接,如果此时有客户请求连接,那么就使用accept接受请求,在Client数组中找到一个空位置初始化并且创建服务线程,服务器端对客户端发来的信息进行分析,默认是群发,调用send_all函数将消息发给其他所有用户,当分析以后得知是私聊信息后,那么调用send_one函数只发送给指定的客户端,每当有用户加入或退出时,服务器端调用 get_num函数统计当前在线人数并在服务器端输出。

Client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#define NAME_SIZE 		(20)//名字的最大字节数
#define CLIENT_COUNT 	(50)//最大人数
#define BUF_SIZE		(4096)//接收消息的最大字节数
void* run(void* arg)
{
	int cli_fd = *(int*)arg;
	char buf[4096];
	for(;;)
	{
		int ret_size = read(cli_fd,buf,sizeof(buf));
		if(0 >= ret_size)
		{
			printf("您已掉线,请检查网络!\n");
			close(cli_fd);
			exit(1);
		}
		
		else {
		printf("\r%s\n>>",buf);
		fflush(stdout);}
	}

	
	
}

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

	// 准备地址
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(6789);//随意,保证客户端和服务端一样就行
	addr.sin_addr.s_addr = inet_addr("192.168.248.55");//这里要修改成自己的ip

	// 连接
	if(connect(cli_fd,(struct sockaddr*)&addr,sizeof(addr)))
	{
		perror("connect");
		return -1;
	}


	char buf[4096] = {};
	printf("请输入姓名:");
	gets(buf);
	write(cli_fd,buf,strlen(buf)+1);

	// 创建接收线程
	pthread_t tid;
	pthread_create(&tid,NULL,run,&cli_fd);
	// 写数据并发送
	 for(;;)
	{
		printf(">>");
		gets(buf);
		int ret_size = write(cli_fd,buf,strlen(buf)+1);
		if(0 >= ret_size)
		{
			printf("您已掉线,请检查网络!\n");
			close(cli_fd);
			return 1;
		}
		else if(0 == strcmp("quit",buf))
		{
			printf("退出聊天室!\n");
			close(cli_fd);
			return 0;
		}
		else if(0 == strcmp("private",buf))
		{
			/*printf("退出聊天室!\n");
			close(cli_fd);*/
		printf("请输入你要私聊的人的昵称:");
                  char toname[NAME_SIZE];
              
                   gets(toname);
                  write(cli_fd,toname,strlen(toname)+1);
                 printf("请输入你要发送的消息:");
                 char bufone[BUF_SIZE];
                  gets(bufone);
		write(cli_fd,bufone,strlen(bufone)+1);
		}
	}
}
Server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

#define NAME_SIZE 		(20)//名字的最大字节数
#define CLIENT_COUNT 	(50)//最大人数
#define BUF_SIZE		(4096)//接收消息的最大字节数
typedef struct Client
{
	int fd;
	struct sockaddr_in addr;
	char name[NAME_SIZE];
}Client;

Client clients[CLIENT_COUNT];//成员集合,存放进来聊天的人的描述符

int  get_num(void)//统计当前人数
{    int k=0;
	for(int i=0; i<CLIENT_COUNT; i++)
	{
		if(0 != clients[i].fd)
		{
			k++;
		}
	}

	return k;
}
Client* get_not_used(void)//遍历成员 查找当前人数是否满
{
	for(int i=0; i<CLIENT_COUNT; i++)
	{
		if(0 == clients[i].fd)
		{
			return &clients[i];//找到空位置,没满,返回
		}
	}

	return NULL;//满了
}

void send_all(Client* client,char* buf)//群发消息
{       
	for(int i=0; i<CLIENT_COUNT; i++)
	{
		if(0 != clients[i].fd && client->fd != clients[i].fd)
		{      
		       
			write(clients[i].fd,buf,strlen(buf)+1);
		}
	}
}
void send_one(Client* client,char* buf,char* toname)//私聊
{
	for(int i=0; i<CLIENT_COUNT; i++)
	{
		
		char buffromname[NAME_SIZE];
		if(0==strcmp(clients[i].name,toname))
		{ 
		   write(clients[i].fd,buf,strlen(buf)+1);}
		
	}
}
void* server(void* arg)
{
	Client* client = arg;
	

	// 准备缓冲区
	char buf[BUF_SIZE];
	char bufone[BUF_SIZE];

	// 接收姓名
	read(client->fd,client->name,NAME_SIZE);
	
	// 拼接欢迎信息
	sprintf(buf,"欢迎来自%s的%s!",inet_ntoa(client->addr.sin_addr),client->name);

	// 告诉所有客户端有人进入
	send_all(client,buf);
         printf("当前在线人数为:%d\n",get_num());
	// 返回欢迎信息
	write(client->fd,"欢迎你进入聊天室!",31);
        	for(;;)
	      {     char buf1[BUF_SIZE];
	           char bufone[BUF_SIZE];
		int ret_size = read(client->fd,buf1,BUF_SIZE);
		if(0 >= ret_size)
		{
			sprintf(buf1,"%s掉线!",client->name);
			close(client->fd);
			client->fd = 0;
			send_all(client,buf1);
		}
		else if(0 == strcmp("quit",buf1))
		{
			sprintf(buf1,"%s退出聊天室!",client->name);
			close(client->fd);
			client->fd = 0;
			 printf("当前在线人数为:%d\n",get_num());
			send_all(client,buf1);
		}
		else if(0 == strcmp("private",buf1)) //给某人单独发消息
		{
			
			char toname[NAME_SIZE];
			char bufone[BUF_SIZE];
			read(client->fd,toname,NAME_SIZE);
			read(client->fd,bufone,BUF_SIZE);
			send_one(client,bufone,toname);
		}
		
		else
		{
			strcat(buf1,":");
			strcat(buf1,client->name);
			send_all(client,buf1);
		}
		
	}
}

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

	// 准备地址
	struct sockaddr_in addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(6789);
	addr.sin_addr.s_addr = inet_addr("192.168.248.55");
	socklen_t addrlen = sizeof(addr);

	// 绑定
	if(bind(svr_fd,(struct sockaddr*)&addr,addrlen))
	{
		perror("bind");
		return -1;
	}

	// 监听
	if(listen(svr_fd,10))
	{
		perror("listen");
		return -1;
	}

	// 等待
	for(;;)
	{
		Client* client = get_not_used();//把新进来的人装进空位置
		if(NULL == client)
		{
			printf("客户端已满!\n");
			sleep(10);
			continue;
		}

		client->fd = accept(svr_fd,(struct sockaddr*)&client->addr,&addrlen);//连接
		if(0 > client->fd)
		{
			perror("accept");
			return 0;
		}

		// 创建服务线程
		pthread_t tid;
		pthread_create(&tid,NULL,server,client);
	}
}

六、实验总结

这个实验实现 linux的 socket 网络编程,socket 简单来说就是 ip+端口。socket用来创建一个通信的端点。返回的是一个文件描述符fd:对于客户端来说,就是通过fd来与服务器来发起通信的,对于服务器来说,这个就是一个监听套接字。bind把socket创建的套接字绑定到指定的ip和port,因为socket系统调用就是告诉操作系统,我要一个通信啦,你要给我准备好来,这样,os就给我们创建了一个基本的数据结构,但是里面还没有填充数据,bind函数就是给数据结构填充数据的。listen把套接字变成监听套接字,对应的TCP状态为状态LISTEN。监听套接字只能用来监听用,也即只能做accept参数。accept套接字默认是阻塞的,当没有连接到来时,服务器一直阻塞在该调用上。当客户一旦调用connect函数,服务端也监听了套接字,那么内核给我们做了连个队列,一个是正在做三次握手的队列,一个是做好三次握手的队列。accept函数就是在做好三次握手队列中拿到一个连接,并且返回一个连接描述符connfd,这样我们就可以通过connfd描述符来与客户端通信了。
做这个实验还要注意read()和write()函数一定要对应起来,一端写了一条消息,另一端就要读消息,因为这两个函数执行调用后,直到返回调用结果才结束,否则一直等待结果,导致进程一直阻塞。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值