一个简单http_server的实现(2)

套接字

写服务器之前我们要知道服务器和终端是怎样通讯的,套接字接口是一组函数,由操作系统实现,通过它可以实现服务器与客户端的通信。

一个套接字地址的数据结构是(定义在netinet/in.h)

1
2
3
4
5
6
7
8
9
10
11
struct sockaddr_in//internet套接字地址
{
unsigned short sin_family;//地址类型总是AF_INET代表因特网地址
unsigned short sin_port;//端口
in_addr sin_addr;//ip地址
unsigned char sin_zero [8];//填充结构体,使得大小和标准格式sockaddr一致
};
struct in_addr
{
unsigned int s_addr;//32位的ip地址
};

在系统提供的套接字接口中,参数是通用的套接字地址结构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是要连接的服务器地址,addrlensizeof(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);
对域名hostnameport端口发起连接,成功返回一个描述符,失败返回-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;//DNS主机条目
if ((clientfd=socket (AF_INET,SOCK_STREAM,0))<0)
return -1;//出错,错误代码是是errno
if ((host=gethostbyname(hostname))==NULL)
return -2;//出错错误代码是h_error
//给套接字地址赋值
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;
//printf("server connect to %s (%s)\n",client->h_name,claddr);
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;
//printf("the context is %s\n",buf);
rio.rio_writen(connectfd,buf,n);//写回去
}
close(connectfd);
}
return 0;
}

测试效果

本文标题:一个简单http_server的实现(2)

文章作者:

发布时间:2020年06月12日 - 15:06

最后更新:2020年06月13日 - 00:06

原始链接:http://startcraft.cn/post/6c5bab24.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------The End-------------