之前在腾讯上使用了一个免费的公网服务器,只有7天,linux系统。
其实有这样的想法,是因为有个研二的师弟问我怎么样才能让连个局域网的电脑通信。
我跟他说了两种方法,一种是找个公网服务器来转发数据,另一种就是UDP打洞。
第二种太难了,所以就用第一种。突然有点想自己实现一下的冲动,于是就搞了一个免费的。
目的是:编写一个服务端,接收一个或者多个客户端。如果一个客户端发送数据,则立刻转发给其他的所有连接上的客户端(除了自己)。
TCP的连接程序自然很简单。定义协议后,服务端就开始监听。但是accep函数会在这一步挂起,如果接受了一个客户端就继续向下走了。
所以需要用多线程来实现。我的思想是,accept一个客户端就继续等待一个客户端的连接。
先上代码:
server.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> #include <signal.h> #include "server.h" int server_sockfd; struct sockaddr_in server_address; int vector_user[MaxUser]; struct sockaddr_in vector_useraddr[MaxUser]; int times=0; pthread_t transferid; typedef void (*sighandler_t)(int); sighandler_t signal(int signum,sighandler_t handler); void sig_int(int sign) { while(1){ printf("SIGINT\n"); } } void sig_pipe(int sign) { while(1){ printf("SIGPIPE\n"); } } int SockInit(void) { int state=-1; server_sockfd=socket(AF_INET,SOCK_STREAM,0); if(server_sockfd<0) { printf("Socket error\n"); return -1; } bzero(&server_address,sizeof(server_address)); server_address.sin_family=AF_INET; server_address.sin_addr.s_addr=htonl(INADDR_ANY);//inet_addr("115.159.196.190"); server_address.sin_port=htons(PORT); state=bind(server_sockfd,(struct sockaddr*)&server_address,sizeof(server_address)); if(state<0) { printf("Bind error\n"); return -1; } state=listen(server_sockfd,2); if(state<0) { printf("Listen error\n"); return -1; } printf("Server is waiting and listening on port:%d\n",PORT); return 0; } int RevData(int cs,char* buf,int maxlen) { int len=0; printf("Ready to get data\n"); read(cs,&len,1); printf("Get %d data\n",len); read(cs,buf,len); return len; } void SendData(int cs,char* buf,int len) { int sendlen=len; send(cs,&sendlen,1,0); send(cs,buf,len,0); } void* TranferData(void* arg) { char buf[1024]={'\0'}; while(1) { int len=RevData(*(int*)arg,buf,1024); int i=0; printf("SocketID is %d: ",*(int*)arg); for(i=0;i<len;i++) { printf("%c",buf[i]); } printf("\n"); for(i=0;i<times;i++) { if((*(int*)arg)!=vector_user[i]) { SendData(vector_user[i],buf,len); } } } return ((void *)0); } void* UserAdd(void* arg) { struct sockaddr_in client_address; socklen_t client_len=sizeof(client_address); while(1) { int client_sockfd=accept(server_sockfd,(struct sockaddr*)&client_address,&client_len); if(client_sockfd<0) { printf("Accpet error\n"); } else { printf("%s:%d is connecting\n",inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port)); vector_user[times]=client_sockfd; vector_useraddr[times]=client_address; int err= pthread_create(&(transferid), NULL, TranferData, (void*)(&vector_user[times])); if ( 0 != err ) { printf("Can't create thread\n"); } ++times; if(times>=MaxUser) { printf("Up to MaxUser\n"); break; } } } return ((void *)0); }
server.h
#ifndef SERVER_H_INCLUDED #define SERVER_H_INCLUDED #define PORT (8888) #define TencentIP ("115.159.196.190") #define MaxUser (1024) int SockInit(void); void* UserAdd(void* arg); int RevData(int cs,char* buf,int maxlen); void SendData(int cs,char* buf,int len); #endif // SERVER_H_INCLUDED
main.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> #include <signal.h> #include "server.h" extern int times; extern int vector_user[MaxUser]; int main() { char buf[1024]; int state=0; state=SockInit(); if(state<0) { printf("Socket init error\n"); return -1; } pthread_t threadid; int err; err = pthread_create(&(threadid), NULL, UserAdd, NULL); if ( 0 != err ) { printf("Can't create thread\n"); return -1; } while(1) { } printf("Hello world!\n"); return 0; }
server.c中
SockInit函数就是用来初始化套接字协议的,没什么可说的,无非就是配置一些参数,然后接听端口。
RevData函数,第一个参数传递是套接字的标识符,因为会有多个标识符。其中,先读一个字符,是因为我定义的协议是先发一个长度,然后再发对应长度的数据。(仅是为了方便)
SendData函数也是一样的道理。
TransferData函数是一个线程函数,传入的参数是套接字的标识符。作用就是把收到的数据转发给除它自己以外所有连接的客户端。
UserAdd函数是一个主要的线程函数。它只开启一次。从while(1)开始就等待accept,如果有连接上,就继续向下走,并且开启TransferData函数线程,之后又进入进的accept,等待新的用户来。每当有用户连接上,都会进入这个状态。
其中有有个全局数组,vector_user,vector_useraddr。是用来储存加入进来的用户标识符和地址。
这也是这个程序没有做好的地方:因为如果有客户端掉线,没有那部分代码来去除数组信息,程序就会错误。这是因为,我懒得写一个vector或者其他的数据结构来管理,如果是c++我就用stl了。
还有一个问题:没有管理好如果有用户突然掉线的情况。可以使用signal来管理,但是时间紧迫,没有完全做好。
总之:对于简单粗暴的数据转发是可以的。但是程序没有优化,很容易错误。只是想体验下linux C的开发。希望以后能有机会能涉及到这方面的项目。学会linux下快速调试。