内容详细的简单群聊

一、本文知识点

基于TCP协议的套接字编程,使用多线程处理多用户。

二、大体框架

服务器:创建套接字->绑定IP->套接字监听->accept用户,给用户创建线程处理程序->分离该线程->关闭套接字。
客户端:创建套接字->连接PIP->读取用户消息->将消息发送给服务端->从服务器接收消息->显示消息给用户
这里写图片描述

三、具体思路

1. 定义一个头文件chat.h,在文件内定义了多个宏和结构体。
 PIP表示客户端使用的ip,IP表示服务器绑定的ip,PORT客户端和服务器相同的端口号。
 NAMESIZE表示用户名字最大长度,MSGSIZE表示消息最大长度,以便于在客户端的buf数组格式化存储用户的名字和消息。
 用户的结构体,该结构体保存用户的名字和消息。
 带头双向链表结构体,保存前一个、后一个节点和自己的套接字变量。
2. 创建DLNode.c文件,内含带头双链表的创建节点,增加和删除节点功能。
3. 客户端创建套接字,并定义一个套接字结构体,使用该结构体调用connect函数和该套接字连接。
 定义buf缓冲区,定义user用户结构体变量。
 将标准输入改为非阻塞态,从标准输入读取内容。读到内容,将用户名和内容格式化保存在user结构体变量内发给服务器,然后非阻塞式接收消息,读到消息格式化取出并打印。
3. 服务器bind绑定IP和PORT,然后置为listen监听态。
 每当accept接收到一个用户,就保存该用户套接字,头插在链表内部。
  创建一个线程去处理该用户,并将该线程置为分离态,无需父进程回收,避免主服务器进程一直阻塞在回收线程上。
  在线程内部,阻塞式接收用户消息,接收到后依次遍历链表,转发消息给每一个用户。当接收到内容长度为0,意味着用户已经退出,则把该套接字从链表内删除并关闭该套接字,最后该线程退出,被系统回收。

三、使用方面

服务器已经绑定了端口和IP无需手动输入,只需执行程序后台转发消息即可。
客户端:内置代码也绑定了IP。第一次执行,需要附带自己的用户名然后执行,之后只需输入消息即可。
注意:本代码中,PIP和IP是我服务器的。如需本地使用,需将PIP和IP都改为自己当前环境的IP的值。只需在一个局域网内,就可实现多人群聊。

四、程序代码

####chat.h

#ifndef __CHAT_H__
#define __CHAT_H__

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

#define IP  "172.27.0.14"//内网ip
#define PIP   "118.24.188.96"//公网ip
#define PORT 8080//端口 
#define MSGSIZE 128//消息大小
#define NAMESIZE 15//姓名长度

typedef struct User
{
		char name[NAMESIZE];//姓名
		char msg[MSGSIZE];//消息
}User;//用户信息

typedef struct DoubleListNode
{
		int value;
		struct DoubleListNode*prev;
		struct DoubleListNode*next;
}DLNode;//带头双向链表

DLNode*CreateNode(int value);//创建节点
void PushFront(DLNode*phead,int value);//头插节点
void Erase(DLNode*phead,int value);//删除节点

#endif//__CHAT_H__

####DLNode.c

#include"chat.h"

DLNode*CreateNode(int value)
{
		DLNode*node=(DLNode*)malloc(sizeof(DLNode));
		node->value=value;
		node->next=NULL;
		node->prev=NULL;
		return node;
}

void PushFront(DLNode*phead,int value)
{
		DLNode*node=CreateNode(value);
		if(phead->next==NULL)
		{
				phead->next=node;
				node->prev=phead;
		}
		else
		{
				node->next=phead->next;
				node->next->prev=node;
				phead->next=node;
				node->prev=phead;
		}
}

void Erase(DLNode*phead,int value)
{
		DLNode*cur=phead->next;
		while(cur!=NULL)
		{
				if(cur->value==value)
				{
						DLNode*prev=cur->prev;
						prev->next=cur->next;
						if(cur->next!=NULL)
								cur->next->prev=prev;
						else
								prev->next=NULL;
						free(cur);
						cur=NULL;
				}
				else
				{
						cur=cur->next;
				}
		}
}

####client.c

#include"chat.h"

int SockDeal()//创建、连接套接字
{
		int sock=socket(AF_INET,SOCK_STREAM,0);
		if(sock<0)
		{
				perror("socket error");
				exit(1);
		}
		struct sockaddr_in server;
		server.sin_family=AF_INET;
		server.sin_port=htons(PORT);
		server.sin_addr.s_addr=inet_addr(IP);

		if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0)
		{
				perror("connet error");
				exit(2);
		}
		return sock;
}

void MsgDeal(int sock,char*name)//处理消息
{
		char buf[NAMESIZE+MSGSIZE]={0};
		User user;
		
		while(1)//消息循环收发
		{
				fflush(stdout);
				memset(buf,0,NAMESIZE+MSGSIZE);
				memset(user.msg,0,MSGSIZE);
				fcntl(0,F_SETFL,FNDELAY);//sock置为非阻塞
				int read_size=read(0,user.msg,MSGSIZE);
				if(read_size>0)
				{
						strcpy(buf,name);  
						strcpy(buf+NAMESIZE,user.msg);   
						if(send(sock,buf,NAMESIZE+MSGSIZE,MSG_DONTWAIT)<0)
						{
								perror("send error");
								exit(1);
						}
				}
				int recv_size=recv(sock,buf,NAMESIZE+MSGSIZE,MSG_DONTWAIT);
				if(recv_size>0)
				{
				memset(user.name,0,NAMESIZE);
				memset(user.msg,0,MSGSIZE);
						strcpy(user.name,buf);
						strcpy(user.msg,buf+NAMESIZE);
						printf("%s 说: %s",user.name,user.msg);
				}
		} 
		close(sock);

}


int main(int argc,char *argv[])
{
		if(argc!=2)
		{
				perror("input format error");
				exit(1);
		}
		int sock=SockDeal();

		MsgDeal(sock,argv[1]);

		close(sock);
		return 0;
}


####server.c

#include"chat.h"

DLNode* phead=NULL;

//将消息群发
void GroupSend(char* buf)
{
	DLNode*cur=phead->next;
	while(cur!=NULL)
	{
		send(cur->value,buf,NAMESIZE+MSGSIZE,MSG_DONTWAIT);
		cur=cur->next;
	}
}

int ServerPrepare()
{
	int sock=socket(AF_INET,SOCK_STREAM,0);
	if(sock<0)
	{
		perror("socker error");
		exit(1);
	}
	struct sockaddr_in host;//服务器主机
	host.sin_family=AF_INET;
	host.sin_port=htons(PORT);
	host.sin_addr.s_addr=inet_addr(IP);

	if(bind(sock,(struct sockaddr*)&host,sizeof(host))<0)
	{
		perror("bind error");
		exit(1);
	}
	if(listen(sock,5)<0)
	{
		perror("listen error");
		exit(1);
	}

	return sock;
}


void thread_fun(void* argument)
{
	int sock=(int)argument;
	char buf[NAMESIZE+MSGSIZE]={0};
	char name[NAMESIZE]="";
	while(1)
	{
		int recv_size=recv(sock,buf,MSGSIZE+NAMESIZE,0);
		if(strlen(buf)>0)
		{
			strncpy(name,buf,NAMESIZE);
		}
		if(recv_size<=0)
		{
			Erase(phead,sock);
			strcpy(buf,"聊天系统");
			sprintf(buf+NAMESIZE,"%s 退出了房间\n",name);
			GroupSend(buf);
			break;
		}
		else
		{
			GroupSend(buf);
		}
	}
	close(sock);
}

void ServerWork(int sock)
{
	while(1)
	{
		int new_sock=accept(sock,NULL,NULL);
		if(new_sock<0)
		{
			perror("accept error");
			return;
		}
		PushFront(phead,new_sock);
		//开始线程处理
		pthread_t thread;
		pthread_create(&thread,NULL,(void*)thread_fun,(void*)new_sock);
		pthread_detach(thread);
	}
}

int main()
{
	int sock=ServerPrepare();
	//将头初始化为sock并没有实在意义(比起随便初始化为一个值,不如初始化为已存在的)
	phead=CreateNode(sock);
	ServerWork(sock);
	close(sock);
	return 0;
}

####makefile

TARGET=client server
all:$(TARGET)
.PHONY:all
client:client.c chat.h
	gcc $^ -o $@
server:server.c DLNode.c chat.h
	gcc $^ -o $@ -lpthread
clean:
	rm -rf $(TARGET)

####五、实测,无图言卵系列
这里写图片描述

最后,欢迎大家留言,提问题给我,谢谢

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值