开源项目:TCP模拟实现QQ群聊(云服务器版)
基于多线程TCP协议模拟实现群聊(云服务器版)
1.思路
①使用链表保存每一个accept返回的socket文件描述符,在服务端给客户端send数据的时候,循环遍历链表,给每个socket文件描述符都发送,从而即可实现简单的群聊
②再将自己的云服务器的外网和内网IP找到,在服务器代码中绑定内网IP,在客户端中绑定外网IP,然后再固定一个开放的端口号(自己设置,一般设置1024之后,如果不自己设置开放的端口号,所有的端口号都可以使用,但是这样是不安全的)
2.云服务器如何开放端口号(以腾讯云为例)
①登录并打开云主机页面,红线标记处就是外网IP和内网IP
②选择“安全组”,点击新建
③有三种选项,模板选择图示中的即可,名称随便改
④点击新建好的安全组,进入图示界面,点击添加规则
⑤按照图示填写即可,端口号最好大于1024
3.碰到的问题
有可能在这里有你写群聊的过程中遇到的困惑的小问题,看看能不能帮助你解决
①为什么一个客户端发送消息后,另一个客户端不能立刻收到,而是得自己发送一次后才能看到上一个人发送的消息?
答:看看是不是客户端中的send和recv设置成阻塞式的了。
②为什么将send和recv都设置成非阻塞的后,还是出现上一个问题?答:如果是用read从标准输入读取键入内容,如read(0,buf,sizeof(buf));,那么就使用fcntl函数将0号文件,即标准输入,更改成非阻塞的。
③为什么会出现发送多条消息后服务器就崩了的情况,只有一个客户端连接的时候还好,有两个或以上的客户端就会这样?答:根据我某个叫小刚的朋友的经历,出现这个问题就是链表没写好,请仔细检查链表的插入和删除,以及空间的释放。
3.代码
代码分成三部分:
protocol.h包含了各种自定义协议(即一些结构体和宏)
client.c是客户端代码
pthread_server.c是服务器代码
注:所有代码都是在通过xshell链接云服务器编写,即代码存储在云服务器中,读者可以自行将服务器写成守护进程,这样只要别人运行你的客户端程序,即可实现群聊
头文件
#ifndef __PROTOCOL_H__
#define __PROTOCOL_H__
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#define NAME 10 //用户名长度
#define MSG 1024 //发送信息大小
#define IP "172.21.0.4" //内网IP
#define PIP "192.144.188.26" //外网IP
#define PORT 2020 //固定的开放的端口号
typedef struct SocketListNode
{
int _sockfd;
struct SocketListNode* _prev;
struct SocketListNode* _next;
}SLN;//带头双向链表,存储所有客户端套接文件描述符
typedef struct User
{
char _name[NAME];//用户名
char _msg[MSG];//用户发送的信息
}user;
#endif //__PROTOCOL_H___
客户端
#include "protocol.h"
int ClientDo(char* argv[])//客户端工作前的准备工作
{
int fd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
if(fd<0)
{
perror("socket");
exit(2);
}
//填充服务器的套接字信息
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr=inet_addr(PIP);
if(connect(fd,(struct sockaddr*)&server,sizeof(server))<0)//建立连接
{
perror("connect");
exit(3);
}
return fd;
}
void working(int sockfd,char* name)//客户端工作ing
{
if(fcntl(0,F_SETFL,FNDELAY)<0)//将0号文件设置成非阻塞的
{
perror("fcntl");
exit(4);
}
user u;
char buf[NAME+MSG]={
0};//存储序列化后的数据
while(1)
{
strncpy(buf,name,NAME);
ssize_t r=read(0,u._msg,MSG);//从标准输入读取键入内容
if(r>0)
{
u._msg[r]=0;
fflush(stdout);
strcpy(buf+NAME,u._msg);
send(sockfd,buf,NAME+MSG,MSG_DONTWAIT);//非阻塞式发送
}
ssize_t rec=recv(sockfd,buf,NAME+MSG,MSG_DONTWAIT);//非阻塞式接收
if(rec>0)
{
strncpy(u._name,buf,NAME);
strncpy(u._msg,buf+NAME,MSG);
printf("%s :>%s",u._name,u._msg);
}
}
close(sockfd);
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
printf("parameter is too less\n");
return 1;
}
int sockfd=ClientDo(argv);
working(sockfd,argv[1]);
return 0;
}
服务端
#include "protocol.h"
SLN* BuyNode(int sockfd)//创建节点
{
SLN* node=(SLN*)malloc(sizeof(SLN));
if(node==NULL)
{
perror("malloc");
exit(1);
}
node->_sockfd=sockfd;
node->_prev=NULL;
node->_next=NULL;
return node;
}
SLN* phead=NULL;//全局的头节点,方便操作
int ServerDo()//服务器工作前的准备工作
{
int fd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
if(fd<0)
{
perror("socket");
exit(2);
}
//填充自己的套接字信息
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr=inet_addr(IP);
if(bind(fd,(struct sockaddr*)&server,sizeof(server))<0)//绑定端口号
{
perror("bind");
exit(3);
}
if(listen(fd,5)<0)//将套接字设置为监听状态
{
perror("listen");
exit(4);
}
return fd;
}
void func(void* arg)//线程函数
{
SLN* node=(SLN*)arg;
SLN* cur=NULL;
char buf[NAME+MSG]={
0};
while(1)
{
ssize_t rec=recv(node->_sockfd,buf,NAME+MSG,0);//阻塞式接收
if(rec<=0)
{
printf("sockfd closed\n");
break;
}
cur=phead->_next;
while(cur!=NULL)//循环给每个套接字都发送
{
send(cur->_sockfd,buf,NAME+MSG,MSG_DONTWAIT);
cur=cur->_next;
}
}
//删除节点,释放空间
node->_prev->_next=node->_next;
if(node->_next!=NULL)
{
node->_next->_prev=node->_prev;
}
node->_prev=NULL;
node->_next=NULL;
free(node);
close(node->_sockfd);
}
void working(int sockfd)//服务器工作ing
{
int new_sockfd=0;
while(1)
{
new_sockfd=accept(sockfd,NULL,NULL);//接收链接
if(new_sockfd<0)
{
perror("accept");
continue;
}
//头插节点,保存客户端套接字
SLN* node=BuyNode(new_sockfd);
node->_next=phead->_next;
node->_prev=phead;
if(phead->_next!=NULL)
{
phead->_next->_prev=node;
}
phead->_next=node;
pthread_t thread=0;
pthread_create(&thread,NULL,(void*)func,(void*)node);
pthread_detach(thread);//将线程设置成分离态,让其“自生自灭”
}
close(sockfd);
}
int main()
{
phead=BuyNode(0);//初始化链表头节点
int sockfd=ServerDo();
working(sockfd);
return 0;
}
还没有评论,来说两句吧...