本博文实现一个类似qq的聊天功能,两个client端连接上服务器,不必知道对方的ip地址,就可以互相发送消息。
**1.Linxu网络编程
2.Linux多线程同步技术**
/*
* pub.h
*
* Created on: 2016年10月30日
* Author: Allen
*/
#ifndef PUB_H_
#define PUB_H_
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFSIZE 32768
int socket_create(int port);
void socket_accept(int listen_st);
void init_socket_client();
void deliver(int index,const char *buf,int len);
void socket_work(int index);
void *socket_contrl(void *arg);
#endif /* PUB_H_ */
/*
* pub.c
*
* Created on: 2016年10月30日
* Author: Allen
*/
#include"pub.h"
int socket_client[2]; //申明2个socket_client,设为全局变量,管理2个client的socket连接
void init_socket_client() //初始化int socket_client[2]数组
{
memset(socket_client, 0, sizeof(socket_client));
}
//接收到来自client发送的消息,index为0,代表接收到socket_client[0]消息,然后给socket_client[1]发送消息
//index为1,代表接收到socket_client[1]消息,然后给socket_client[0]发送消息
//buf为发送消息内容,len为发送消息长度
void deliver(int index, const char *buf, int len)
{
int rc = 0;
if (index == 0) //如果 index为0,代表0给1发消息
{
if (socket_client[1] == 0) //1不在线
{
printf("%d : user not online\n", index);
} else
{
rc = send(socket_client[1], buf, len, 0); //将消息下发给1
if (rc <= 0)
{
if (rc < 0)
printf("send faied %s\n", strerror(errno));
else
printf("socket disconnect %s\n", strerror(errno));
}
}
}
if (index == 1)
{
if (socket_client[0] == 0)
{
printf("%d : user not online\n", index);
} else
{
rc = send(socket_client[0], buf, len, 0);
if (rc <= 0)
{
if (rc < 0)
printf("send faied %s\n", strerror(errno));
else
printf("socket disconnect %s\n", strerror(errno));
}
}
}
}
//接收来自client端的连接,index为0代表接收来自socket_client[0]的消息
//index为1代表接收来自socket_client[1]的消息
void socket_work(int index)
{
char buf[BUFSIZE];
int rc = 0;
while (1)
{
memset(buf, 0, sizeof(buf));
rc = recv(socket_client[index], buf, BUFSIZE, 0);
if (rc <= 0) //client连接断开
{
if (rc < 0)
printf("%d:recv failed %s\n", index, strerror(errno));
else
printf("%d:socket disconnect\n", index);
close(socket_client[index]);
socket_client[index] = 0; //如果client的socket已经断开,那么置socket_client[]数组中相应的元素为0
break;
} else
{
printf("%d:recv is '%s'\nrecv %d bytes\n", index, buf, rc);
//index为0,代表接收到socket_client[0]消息,然后给socket_client[1]发送消息
//index为1,代表接收到socket_client[1]消息,然后给socket_client[0]发送消息
//buf为发送消息内容
deliver(index, buf, rc);
}
}
}
void *socket_contrl(void *arg) //server端的线程入口函数
{
int client_st = *(int *) arg; //得到从accept函数返回的来自client端的socket描述符
free((int*) arg);
printf("control thread begin\n");
int index = 0;
if (socket_client[0] == 0)//如果socket_client[0]空闲,就将来自client端的socket赋给socket_client[0]
{
socket_client[0] = client_st;
} else //如果socket_client[1]空闲,就将来自client端的socket赋给socket_client[1]
{
if (socket_client[1] == 0)
{
socket_client[1] = client_st;
index = 1;
} else //socket_client[0]socket_client[1]都不空闲
{
printf("connect full\n");
close(client_st); //socket_client[2]两个成员都已经在线了,拒绝其他client连接
return NULL;
}
}
//接收来自client端的连接,index为0代表接收来自socket_client[0]的消息
//index为1代表接收来自socket_client[1]的消息
socket_work(index);
printf("control thread end\n");
return NULL;
}
int socket_create(int port) //创建server端socket在port指定端口listen
{
int st = socket(AF_INET, SOCK_STREAM, 0); //创建TCP socket描述符
if (st == -1)
{
printf("socket failed %s\n", strerror(errno));
return -1;
}
int on = 1;
if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
{
printf("setsockopt failed %s\n", strerror(errno));
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if ((bind(st, (struct sockaddr*) &addr, sizeof(addr)) == -1)) //在port端口上绑定
{
printf("bind failed %s\n", strerror(errno));
return -1;
}
if (listen(st, 20) == -1) //开始listen
{
printf("listen failed %s\n", strerror(errno));
return -1;
}
return st; //返回listen的server端socket描述符
}
void socketaddr_toa(const struct sockaddr_in*addr, char *IPAddr)//将addr转化为字符串IPAddr
{
unsigned char *p = (unsigned char*) &(addr->sin_addr.s_addr);
sprintf(IPAddr, "%u.%u.%u.%u", p[0], p[1], p[2], p[3]);
}
void socket_accept(int listen_st) //server端socket的accept函数
{
pthread_t thd;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //设置线程属性为可分离
struct sockaddr_in cliaddr;
while (1)
{
memset(&cliaddr, 0, sizeof(cliaddr));
socklen_t len = sizeof(cliaddr);
int client_st = accept(listen_st, (struct sockaddr*) &cliaddr, &len); //accept函数阻塞,直到有client的socket连接到达
if (client_st == -1)
{
printf("accept failed %s\n", strerror(errno));
break;
} else
{
char sIP[32];
memset(sIP, 0, sizeof(sIP));
socketaddr_toa(&cliaddr, sIP);
printf("accept by %s\n", sIP);
int *tmp = malloc(sizeof(int));
*tmp = client_st;
//只要accept到来自client端的socket,就启动一个线程,线程入口函数为socket_contrl
pthread_create(&thd, &attr, socket_contrl, tmp);
}
}
pthread_attr_destroy(&attr);
}
/*
* qqclient.c
*
* Created on: 2016年10月30日
* Author: Allen
*/
#include"pub.h"
int socket_connect(const char*hostname,int port)client端socket连接到hostname指定的IP地址和port端口号
{
int client_st=socket(AF_INET,SOCK_STREAM,0);//建立一个TCP socket描述符
if(client_st==-1)
{
printf("client_st socket failed %s\n",strerror(errno));
return -1;
}
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(port);
addr.sin_addr.s_addr=inet_addr(hostname);
if((connect(client_st,(struct sockaddr*)&addr,sizeof(addr)))==-1)//连接到指定IP
{
printf("connect failed %s\n",strerror(errno));
return -1;
}else
{
printf("connect to %s:%d success\n",hostname,port);
}
return client_st;//连接成功后返回socket描述符
}
void *socket_read(void *arg)//读socket数据的线程入口函数
{
int st=*(int *)arg;
char buf[BUFSIZE];
while(1)
{
memset(buf,0,BUFSIZE);
int rc=recv(st,buf,BUFSIZE,0);//循环从socket中读取数据
if(rc<=0)//如果recv失败,代表TCP连接中断,循环break
{
printf("recv failed %s\n",strerror(errno));
break;
}else//将从socket读取到 的数据打印到屏幕
{
printf("recv '%s'\nrecv %d bytes\n",buf,rc);
}
}
return NULL;
}
void *socket_write(void *arg)//写socket数据的线程入口函数
{
int st=*(int*)arg;
char buf[BUFSIZE];
while(1)
{
memset(buf,0,BUFSIZE);
read(STDIN_FILENO,buf,BUFSIZE);//从键盘读取数据,放入buf中
int ilen=strlen(buf);
if(buf[ilen-1]=='\n')//如果读到的数据最后为回车键,那么把回车键替换为空字符'\0'
{
buf[ilen-1]=0;
}
int rc=send(st,buf,strlen(buf),0);//将从键盘读取到的字符发送出去
printf("send '%s'\nsend %d bytes\n",buf,rc);
if(rc<=0)
{
printf("send failed %s\n",strerror(errno));
break;
}
}
return NULL;
}
int main(int arg,char *args[])
{
if(arg<3)
{
printf("usage:qqclient hostname port\n");
return 0;
}
int port=atoi(args[2]);
if(port==0)
{
printf("port is invalid\n");
return 0;
}
printf("qqclient begin\n");
int st=socket_connect(args[1],port);//第一个参数为IP地址,连接到第一个参数指定的IP地址
if(st==-1)//连接失败,main函数退出
{
printf("socket connect failed\n");
return 0;
}
pthread_t thr_read,thr_write;
pthread_create(&thr_read,0,socket_read,&st);//启动读socket数据线程
pthread_create(&thr_write,0,socket_write,&st);//启动写socket数据线程
pthread_join(thr_read,NULL);
//pthread_join(thr_write, NULL);//如果等待thr_write退出,main函数可能被挂起
close(st);
printf("qqclient end\n");
return 0;
}
/*
============================================================================
Name : qqserver.c
Author : Allen
Version :
Copyright : Your copyright notice
Description : Hello World in C, Ansi-style
============================================================================
*/
#include"pub.h"
int main(int arg,char*args[])
{
if(arg<2)
{
printf("usage:qqserver port\n");
return 0;
}
int port=atoi(args[1]);
if(port==0)
{
printf("port is invalid\n");
return 0;
}
printf("qqserver begin\n");
init_socket_client();//初始化int socket_client[2]数组
int listen_st=socket_create(port);//建立server端socket,在port指定的端口号上listen
if(listen_st==-1)//如果建立失败,main函数退出
{
printf("socket create failed\n");
return EXIT_FAILURE;
}
socket_accept(listen_st);//server端socket开始accept
close(listen_st);
printf("qqserver end\n");
return 0;
}