一、本文知识点
基于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)
####五、实测,无图言卵系列
最后,欢迎大家留言,提问题给我,谢谢