一直好奇QQ的通讯过程是怎么实现的,刚学了点linux下的socket编程,所以也想试着模仿下。QQ登陆起初我是想用tcp有连接的方式i,但是发现QQ登陆时并不是一直和服务器连接这的,要不然太耗费服务器资源,应该是服务器每隔多长时间发送一个心跳包,来检测用户是否在线。写的比较简陋,有时间继续完善。
服务器截图:
客户端1:格式 <ip> <port> <frm> <to>
客户端2:格式 <ip> <port> <frm> <to>
下面是实现的源代码
封装的消息包:
struct message
{
int from;
int to;
char time[30];
char content[256];
};
struct people
{
int id;
bool is_online;
char IP[32];
int port;
char name[10];
};
//get the current time
void get_time(char *buf)
{
time_t timep;
struct tm *p;
time(&timep);
p = gmtime(&timep);
sprintf(buf, "%d : %d : %d", p->tm_hour, p->tm_min, p->tm_sec);
}
服务器的源代码:
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h> //fork()
#include <pthread.h>
#include "message.h"
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
struct sockaddr_in s_addr, c_addr;
char* s_addr_ip = (char*)"0.0.0.0";
int s_port = 8888;
int sock;
int len;
socklen_t addr_len;
char buf[124];
struct message ms;
struct people peo;
vector<struct people> peo_set;
queue<struct message> ms_set;
pthread_t thread_id;
int find(vector<struct people> peo_set, int id)
{
// vector<struct people>::iteartor it = peo_set.begin();
for(int i=0; i< peo_set.size(); ++i)
if( peo_set[i].id == id )
return i;
return -1;
}
void send_ms()
{
memset(buf,0, sizeof(buf));
struct sockaddr_in rs;
int socket;
int str;
int id;
while( !ms_set.empty())
{
ms = ms_set.front();
//printf("from %id to %d time %s message %s", ms.from, ms.to, ms.time, ms.content);
if( (id = find(peo_set, ms.to)) == -1 )
{
printf("the man is notonline");
}
else
{
printf("rsend: begin");
//printf("the message to %d ip %s %d", id, peo_set[id].IP, peo_set[id].port);
memset(&rs, 0, sizeof(rs));
rs.sin_family = AF_INET;
rs.sin_addr.s_addr = inet_addr(peo_set[id].IP);
rs.sin_port = htons(peo_set[id].port);
len = sendto(sock, &ms, sizeof(ms), 0, (struct sockaddr*) &rs, addr_len);
if(len < 0)
{
printf("rsent error");
}
}
ms_set.pop();
}
}
int main(int argc, char* argv[])
{
int pid;
if(argc == 2)
{
s_addr_ip = argv[1];
}
if(argc == 3)
{
s_addr_ip = argv[1];
s_port = atoi(argv[2]);
}
sock = socket(AF_INET, SOCK_DGRAM, 0);
if(sock == -1)
{
perror("socket");
return -1;
}
memset(buf, 0, sizeof(buf));
memset(&s_addr, 0, sizeof(s_addr));
s_addr.sin_family = AF_INET;
s_addr.sin_addr.s_addr = inet_addr(s_addr_ip);
s_addr.sin_port = htons(s_port);
if(bind(sock, (struct sockaddr*)&s_addr, sizeof(s_addr)) == -1)
{
perror("bind");
return -2;
}
addr_len = sizeof(c_addr);
len = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*) &c_addr, &addr_len);
printf("the new id %s", buf);
peo.id = atoi(buf);
strcpy(peo.IP, inet_ntoa(c_addr.sin_addr));
peo.port = ntohs(c_addr.sin_port);
peo.is_online = true;
peo_set.push_back(peo);
len = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*) &c_addr, &addr_len);
printf("the new id %s", buf);
peo.id = atoi(buf);
strcpy(peo.IP, inet_ntoa(c_addr.sin_addr));
peo.port = ntohs(c_addr.sin_port);
peo.is_online = true;
peo_set.push_back(peo);
// pthread_create(&thread_id, NULL, send_ms, NULL);
while(1)
{
len = recvfrom(sock, &ms, sizeof(ms), 0, (struct sockaddr*) &c_addr, &addr_len);
if(len < 0)
{
perror("recvfrom");
}
buf[len] = '\0';
if(len == 0 )
{
printf("the other one close quit\n");
}
else
{
printf("recvim come form %s:%d \n", inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
printf("from %d to %d time %s message %s", ms.from, ms.to, ms.time, ms.content);
}
ms_set.push(ms);
send_ms();
}
return 0;
}
客户端:
#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <resolv.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "message.h"
using namespace std;
int main(int argc, char* argv[])
{
struct sockaddr_in s_addr;
socklen_t socklen;
int sock;
int len;
char buf[125];
char* ser_addr = (char*)"127.0.0.1";
int ser_port = 8888;
pid_t pid;
struct message ms;
struct people peo;
int from, to;
if(argc == 2)
{
ser_addr = argv[1];
}
if(argc == 3)
{
ser_addr = argv[1];
ser_port = atoi(argv[2]);
}
if(argc == 4)
{
ser_addr = argv[1];
ser_port = atoi(argv[2]);
from = atoi(argv[3]);
}
if(argc == 5)
{
ser_addr = argv[1];
ser_port = atoi(argv[2]);
from = atoi(argv[3]);
to = atoi(argv[4]);
ms.from = from;
ms.to = to;
}
if( (sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("-1");
// exit(errno);
return -1;
}
else
{
printf("create socket\n");
}
s_addr.sin_family = AF_INET;
s_addr.sin_addr.s_addr = inet_addr(ser_addr);
s_addr.sin_port = htons(ser_port);
memset(buf, 0, sizeof(buf));
printf("input you id");
fgets(buf, sizeof(buf), stdin);
len = sendto(sock, &buf, strlen(buf), 0, (struct sockaddr*) &s_addr, sizeof(struct sockaddr_in));
if(len < 0 )
{
printf("you id is not know");
return -1;
}
if( -1 == (pid = fork()))
{
perror("fork");
exit(EXIT_FAILURE);
}
else if( pid > 0)
{
while(1)
{
printf("pls send message to send:\n");
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
strcpy(ms.content, buf);
get_time(ms.time);
if(!strncasecmp(buf, "quit", 4))
{
printf("i will quit\n");
break;
}
len = sendto(sock, &ms, sizeof(ms), 0, (struct sockaddr*) &s_addr, sizeof(struct sockaddr_in));
if(len < 0)
{
printf("rsend error");
return 3;
}
}
}
else if(pid == 0)
{
while(1)
{
len = recvfrom(sock, &ms, sizeof(ms), 0, (struct sockaddr*) &s_addr, &socklen);
buf[len] = '\0';
printf("receive from service %s:%d message:\n", inet_ntoa(s_addr.sin_addr), ntohs(s_addr.sin_port));
printf("from %d to %d \n time %s message %s\n", ms.from, ms.to, ms.time, ms.content);
}
}
return 0;
}
用到的函数
1. Inet_addr()
inet_addr()的功能是将一个点分十进制的IP转换成一个长整数型数(u_long类型)
原型:in_addr_t inet_addr(const char *cp);
参数:字符串,一个点分十进制的IP地址
返回值:
如果正确执行将返回一个无符号长整数型数。如果传入的字符串不是一个合法的IP地址,将返回INADDR_NONE。
头文件:Winsock2.h.
arpa/inet.h(Linux)
2. Inet_ntoa()
功能:
将一个IP转换成一个互联网标准点分格式的字符串。
原型:
char FAR * inet_ntoa( struct in_addr in);
头文件:
arpa/inet.h
Winsock2.h
参数:
一个网络上的IP地址
返回值:
如果正确,返回一个字符指针,指向一块存储着点分格式IP地址的静态缓冲区(同一线程内共享此内存);错误,返回NULL。
3 htons()
将主机的无符号短整形数转换成网络字节顺序。
#include <winsock.h>
u_short PASCAL FAR htons( u_short hostshort);
hostshort:主机字节顺序表达的16位数。
注释:
本函数将一个16位数从主机字节顺序转换成网络字节顺序。
返回值:
htons()返回一个网络字节顺序的值。
4 ntohs()
将一个无符号短整形数从网络字节顺序转换为主机字节顺序。
#include <netinet/in.h>
uint16_t ntohs(uint16_t netshort);
netshort:一个以网络字节顺序表达的16位数。
5 recvfrom函数(经socket接收数据):
函数原型:ssize_t recvfrom(int sockfd,void *buf,intlen,unsigned int flags, struct sockaddr *from,socket_t *fromlen);
ssize_t 相当于 int,socket_t 相当于int,这里用这个名字为的是提高代码的自说明性。
6 int sendto(int sockfd, const void*msg,int len unsigned int flags, const structsockaddr *to, int tolen); 该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。
Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。
|