编写一个linux下的聊天室。
分析:需要服务器接收消息并转发给每一个连接上的套接字。需要用多线程,后台线程接收,前台线程发送。
client.c
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/un.h>
#include<sys/time.h>
#include<sys/ioctl.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
char recv_buf[1500],send_buf[1024];
void pthread_function(int sockfd)
{
int recvbytes;
while(1)
{
memset(recv_buf,1500,0);
if(recvbytes=recv(sockfd,recv_buf,1500,0)==-1)
{
perror("recv error!\n");
exit(1);
}
else
{
printf("%s\n",recv_buf);
}
}
}//新的线程一直接收数据并输出,直到接收失败(linux里面recv接收到的字符串没有\0)
int main(void)
{
//char ip[20];
//printf("please input ip");
//gets(ip);
pthread_t id;
int sockfd;
struct sockaddr_in server_addr ;
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(12345);
server_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
perror("socket error!\n");
exit(1);
}
if(connect(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr))==-1)
{
perror("connect error!\n");
exit(1);
}
char name[20];
printf("input your name:");
scanf("%s",name);
send(sockfd,name,strlen(name),0);//发送名字
pthread_create(&id,NULL,(void *)pthread_function,(int *)sockfd);//开启新的线程运行接收函数
while(1)
{
memset(send_buf,1500,0);
gets(send_buf);
if(send(sockfd,send_buf,strlen(send_buf),0)==-1)
{
perror("send error!\n");
exit(1);
}
sleep(1);
}
//关闭套接字,并取消线程
close(sockfd);
pthread_cancel(id);
return 0;
}//后台线程负责接收,前台线程负责发送。
server.c
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/un.h>
#include<sys/time.h>
#include<sys/ioctl.h>
#include<unistd.h>
#include<netinet/in.h>
#include<pthread.h>
#define COUNT 5//同时在线人数5
//保存socket
int socket_fd[COUNT];
void pthread_function(int client_fd)
{
char message[1500];
char buf[1024];
int i,recvbytes;
char name[20];
//首次连接时保存名字
memset(name,20,0);
recvbytes=recv(client_fd,name,20,0);//接收名字
//接收name
name[recvbytes]=':';
//构造接收内容字符串
while(1)
{
memset(buf,1024,0);
if((recvbytes=recv(client_fd,buf,1024,0))==-1)
{
printf("recv error!\n");
exit(1);
}
if(recvbytes==0)
{
printf("%sbye!\n",name);//recv函数返回值为0表示
break;
}
for(i=0;i<COUNT;i++)
{
if(socket_fd[i]==-1)
continue;//对应的socket为-1表示连接已经断开
else
{
memset(message,1500,0);
strcpy(message,name);
strcat(message,buf);
if(send(socket_fd[i],message,strlen(message),0)==-1)//接收完消息把消息的内容发送给每个套接字
{
perror("send error!\n");
exit(1);
}
}
}
}
close(client_fd);
for(i=0;i<COUNT;i++)
{
if(socket_fd[i]==client_fd)
{
socket_fd[i]=-1;
}
}
//关闭套接字并把sock_fd设置为-1
pthread_exit(NULL);
//关闭线程
}
//新的线程函数,直到接收不到消息才会断开连接 ,否则会一直接收并把收到的消息发送给每一个套接字
int main()
{
int i;
for(i=0;i<COUNT;i++)
{
socket_fd[i]=-1;
}//初始化socket_fd 字符串
pthread_t id;
int sockfd,client_fd;
socklen_t sin_size;
struct sockaddr_in my_addr;
struct sockaddr_in remote_addr;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)//创建套接字
{
perror("socket error!\n");
exit(1);
}//套接字就是一个int型的标识
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(12345);
my_addr.sin_addr.s_addr=INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
if(bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr))==-1)
{
perror("bind error!\n");
exit(1);
}
if(listen(sockfd,10)==-1)
{
perror("listen failed!\n");
exit(1);
}
i=0;
while(1)
{
sin_size=sizeof(struct sockaddr_in);
client_fd=accept(sockfd,(struct sockaddr*)&remote_addr,&sin_size);
if(client_fd==-1)
{
perror("accept error!\n");
exit(1);
}
//找到一个可用的socket位置
while(socket_fd[i]!=-1)
{
i=(i+1)%COUNT;
}
//保存socket并开启线程处理
socket_fd[i]=client_fd;
pthread_create(&id,NULL,(void *)pthread_function,(int *)client_fd);
}
}
服务器端维护一个客户端连接的套接字数组。客户端需要连接。
注意:
if((client_fd=accept(sockfd,(struct sockaddr*)&remote_addr,&sin_size))==-1)
{
perror("accept error!\n");
exit(1);
}
这样写看似没问题但经过编译器优化后就不一样了它会把套接字client_fd设置为0,仔细用gdb跟一下就能得出结论。(我这里又找不到错误了,就不演示了)。
发现:接收失败或者是发送失败,看一下函数没问题就要思考套接字有问题了。可能套接字不知道什么时候被清零了(linux下的套接字是int型,容易被改变)。
这里为了调试方便,编译的时候把保护全部关闭,并且不让编译器进行太多优化
gcc -O0 -fno-stack-protector -z execstack -z norelro -no-pie server-l.c -lpthread -o server
关于关闭各种保护机制:
gcc -z norelro norelro
gcc编译时,关闭DEP和栈保护,-fno-stack-protector和-z execstack这两个参数会分别关掉DEP和Stack Protector
gcc -fno-stack-protector -z execstack -o level1 level1.c
-no-pie 关掉pie
这里附上链接了解更多详情https://blog.csdn.net/nibiru_holmes/article/details/61209297