套接字
写服务器之前我们要知道服务器和终端是怎样通讯的,套接字接口是一组函数,由操作系统实现,通过它可以实现服务器与客户端的通信。
一个套接字地址的数据结构是(定义在netinet/in.h)
1 2 3 4 5 6 7 8 9 10 11
| struct sockaddr_in { unsigned short sin_family; unsigned short sin_port; in_addr sin_addr; unsigned char sin_zero [8]; }; struct in_addr { unsigned int s_addr; };
|
在系统提供的套接字接口中,参数是通用的套接字地址结构sockaddr(定义在socket.h),需要强制转换
1 2 3 4 5
| struct sockaddr { unsigned short sa_family; char sa data[14]; }
|
socket函数
函数原型:int socket(int domain,int type,int protocol);
函数返回一个套接字描述符,也就是一个文件描述符
fd=socket(AF_INET,SOCK_STREAM,0)``AF_INET表示因特网,SOCK_STREAM表示这个套接字是因特网连接的一个端点
connect函数
客户端通过调用connect函数来与服务器建立连接
函数原型int connect(int sockfd,struct sockaddr* serv_addr,int addrlen)
sockfd是一个套接字描述符,serv_addr是要连接的服务器地址,addrlen是sizeof(addrlen)
该函数成功返回0,失败返回-1,成功后客户端就可以读写sockfd了
bind函数
函数原型:int bind(int sockfd,struct sockaddr* my_addr,int addrlen);
作用是将my_addr表示的服务器套接字地址和sockfd绑定
成功返回0,出错返回-1
listen函数
函数原型:int listen(int sockfd,int backlog);
作用是将sockfd转换成一个监听套接字,客户端会对监听套接字发起请求,成功返回0,失败返回-1,backlog是等待队列的长度
accept函数
函数原型int accept (int listenfd,struct sockaddr *addr,int *addrlen);
该函数等待来自客户端的连接到达监听描述符listenfd,然后将客户端的套接字地址写入addr,成功返回一个已连接描述符,服务器通过读写已连接描述符和客户端通信,失败则返回-1
通过上面这些函数我们可以整合出两个函数,一个再服务器指定端口打开监听描述符,一个在客户端对指定地址和端口发起连接
open_listenfd函数
函数原型int open_listenfd(int prot);
成功返回一个在port端口打开的监听描述符,失败返回-1
open_clientfd函数
函数原型int open_clientfd(char *hostname,int port);
对域名hostname的port端口发起连接,成功返回一个描述符,失败返回-1
上面两个函数的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include "my_socket.h" #define LISTENQ 1024 int MySocket::openClientfd(char* hostname,int port) { int clientfd; sockaddr_in serveraddr; hostent *host; if ((clientfd=socket (AF_INET,SOCK_STREAM,0))<0) return -1; if ((host=gethostbyname(hostname))==NULL) return -2; memset(&serveraddr,0,sizeof(serveraddr)); serveraddr.sin_family=AF_INET; serveraddr.sin_port=htons(port); memcpy(&serveraddr.sin_addr.s_addr,host->h_addr_list[0],host->h_length); if (connect(clientfd,(sockaddr*)&serveraddr,sizeof(serveraddr))<0) return -1; return clientfd; }
int MySocket::openListenfd(int port) { int listenfd,optval=1; sockaddr_in serveraddr; if ((listenfd=socket(AF_INET,SOCK_STREAM,0))<0) return -1; if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,(const void *)&optval,sizeof(int))<0) return -1; memset(&serveraddr,0,sizeof(serveraddr)); serveraddr.sin_family=AF_INET; serveraddr.sin_port=htons(port); serveraddr.sin_addr.s_addr=htonl(INADDR_ANY); if(bind(listenfd,(sockaddr*)&serveraddr,sizeof(serveraddr))<0) return -1; if(listen(listenfd,LISTENQ)<0) return -1; return listenfd; }
|
测试
写了两个小demo来进行测试
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include "http_server.h" #include "rio.h" #include "my_socket.h" #define MAXLEN 200 int main () { char hostname[10]="localhost"; int clientfd=MySocket::openClientfd(hostname,8888); rio_t riobuffer(clientfd); char buf[MAXLEN]; Rio rio; while(fgets(buf,MAXLEN,stdin)!=NULL) { int len=strlen(buf); rio.rio_writen(clientfd,buf,len); int n=rio.rio_readlineb(&riobuffer,buf,MAXLEN); std::cout<<"client recived "<<n<<" bytes"<<std::endl; std::cout<<"the content is "<<buf<<std::endl; } close(clientfd); return 0; }
|
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include "rio.h" #include "my_socket.h" #include <iostream> #define MAXLEN 200 int main () { int listenfd=MySocket::openListenfd (8888); sockaddr_in clientaddr; unsigned int clientlen; hostent *client; memset(&clientaddr,0,sizeof(clientaddr)); rio_t riobuffer(0); while(true) { clientlen=sizeof(clientaddr); int connectfd=accept(listenfd,(sockaddr*)&clientaddr,&clientlen); client=gethostbyaddr((const char *)&(clientaddr.sin_addr.s_addr),sizeof(clientaddr.sin_addr.s_addr),0); char *claddr=inet_ntoa(clientaddr.sin_addr); std::cout<<"server connect to "<<client->h_name<<"("<<claddr<<")"<<std::endl; Rio rio; rio.rio_readinitb(&riobuffer,connectfd); char buf[MAXLEN]; int n; while((n=rio.rio_readlineb(&riobuffer,buf,MAXLEN))>0) { std::cout<<"server received "<<n<<" bytes"<<std::endl; std::cout<<"the context is "<<buf<<std::endl; rio.rio_writen(connectfd,buf,n); } close(connectfd); } return 0; }
|
测试效果