CSAPP(11)Network Programming

文章目录

  • The Client-Server Programming Model
  • Networks
  • The Global IP Internet
    • DNS
  • The Sockets Interface
    • client
    • server
    • accept
  • Web Servers
  • The Tiny Web Server

The Client-Server Programming Model

Networks

hub会把收到的frame发向所有port,但是bridge会通过学习,把数据只发向目的端口(或丢弃)

The Global IP Internet

网络上一般采用big-endian传输数据。

DNS

struct{
	char	*h_name;//official domain name
	char	**h_aliases;//Null-terminated array of domain names
	int		h_addrtype;//host address type
	int		h_length;
	char	**h_addr_list;
}

#include 
// return non-NULL pointer is OK,NULL pointer on error with h_error set
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const char *addr,int len,0);

The Sockets Interface

client

从客户端向服务端连接时先使用socket来创建(第一个参数AF_INET表示ip v4,ip v6则使用AF_INET6,第二个参数总是SOCK_STREAM表示这是一个endpoint),再由connect来完成真正的链接(连接时会block)

#include 
#include 
//return nonnegative descriptor if OK,-1 on error
int socket(int domain,int type,int protocol);
//return 0 if ok,-1 on error
int connect(int sockfd,struct sockaddr *serv_addr,int addrlen);

客户端向服务端的链接的多个步骤可以封装到一起

int open_clientfd(char *hostname,int port){
	int clientfd;
	struct hostent *hp;
	struct socketaddr_in serveraddr;
	if((clientfd==socket(AF_INET,SOCK_STREAM,0))<0)
		return -1;//check errno for cause
	if((hp=gethostbyname(hostname))==NULL)
		return -2;
	bzero((char *)&serveraddr,sizeof(serveraddr));
	serveraddr.sin_family=AF_INET;
	bcopy((char *)hp->h_addr_list[0],(char *)&serveraddr.sin_addr.s_addr,hp->h_length);
	serveraddr.sin_port=htons(port);//转成big-endian
	if(connect(clientfd,(SA *)&serveraddr,sizeof(serveraddr))<0)
		return -1;
	return clientfd;
}

server

服务端一般使用bind来指定端口(addrlen的取值是sizeof(socketaddr_in)),通过listen来将active socket转化成listening socket(其中backlog是指最大等待数)

#include 
int bind(int sockfd,struct sockaddr *my_addr,int addrlen);
int listen(int sockfd,int backlog);

同样,服务端监听端口也可以合并成一个函数

int open_listenfd(int port){
	int listenfd,optval=1;
	struct 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;
	bzero((char *)&serveraddr,sizeof(serveraddr));
	serveraddr.sin_family=AF_INET;
	serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);//接受来自任何地址的连接
	serveraddr.sin_port=htons((unsigned short)port);
	if(bind(listenfd,(SA*)&serveraddr,sizeof(serveraddr))<0)
		return -1;
	if(listen(listenfd,LISTENQ)<0)
		return -1;
	return listenfd;
}

accept

上面对于server而言只能算是分配了资源,真正让client可以连上server还需要server调用下面的accept函数

#include 
//return nonnegative connected descriptor if ok,-1 on error
int accept(int listenfd,struct sockaddr *addr,int *addrlen);

当server调用accept后就会进入等待状态(下图一),然后client发起请求(下图二),然后建立连接,server的accept和client的connect都会返回(下图三),各自得到descriptor用于后面操作。

#include 
#include 
#include 
#include 

int Open_listenfd(int port){
    int out,optval=1;
    struct sockaddr_in serveraddr;
    if((out=socket(AF_INET,SOCK_STREAM,0))<0){
        return -1;
    }
    if(setsockopt(out,SOL_SOCKET,SO_REUSEADDR,(const void *)&optval,sizeof(int ))<0){
        return -1;
    }
    bzero((char *)&serveraddr,sizeof(serveraddr));
    serveraddr.sin_family=AF_INET;
    serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
    serveraddr.sin_port=htons((unsigned short )port);
    if(bind(out,(__CONST_SOCKADDR_ARG)&serveraddr,sizeof(serveraddr))<0){
        return -1;
    }
    if(listen(out,LISTENQ)<0)
        return -1;
    return out;
}
void doit(int fd){
    int is_static;
    struct stat sbuf;
    char buf[MAXLINE],method[MAXLINE],uri[MAXLINE],version[MAXLINE];
    char filename[MAXLINE],cgiargs[MAXLINE];
    rio_t rio;
    rio_readinitb(&rio,fd);
    rio_readlineb(&rio,buf,MAXLINE);
    sscanf(buf,"%s %s %s",method,uri,version);
    if(strcasecmp(method,"GET")){
        clienterror(fd,method,"501","Not Implemented","does not implement this method")
    }
}
int main(int argc,char **argv) {
    int listenfd,connfd,port,clientlen;
    struct sockaddr_in clientaddr;
    if(2!=argc){
        fprintf(stderr,"usage:%s \n",argv[0]);
        return 1;
    }
    port=atoi(argv[1]);
    listenfd=Open_listenfd(port);
    while (1){
     clientlen=sizeof(clientaddr);
     connfd=accept(listenfd,&clientaddr,&clientlen);
     doit(connfd);
     close(connfd);
    }
}

Web Servers

在我们调试网络服务时,可以通过telnet指令来模拟客户端
在http header里有Host这一项是为了方便代理服务器来寻找自己是否有缓存使用
对于静态资源,server直接返回即可,对于动态资源则会使用Common Gateway Interface来处理。这个时候server会fork一个进程,使用dup2来重定向standard output(因为这是cgi输出的地方)通过设置环境变量(如下表),然后调用execve来执行*/cgi-bin/adder来处理,而cgi会通过getenv*来获取设置的环境变量,这就是CGI标准.

  • QUERY_STRING
  • SERVER_PORT
  • REQUEST_METHOD
  • REMOTE_HOST
  • REMOTE_ADDR
  • CONTENT_TYPE
  • CONTENT_LENGTH

而作为cgi程序除了需要返回content外还需要返回content-type,content-length等信息。可以实现使用sprintf来存好数据,再使用printf来写入stantard out,最后使用fflush刷缓存

The Tiny Web Server

文中给出了一个示例,但是也指出对于一个真正的server而言需要处理各种意外情况,比如客户端断开链接而导致的SIGPIPE

你可能感兴趣的:(底层知识,Network,CSAPP,socket,web,server)