CSAPP(11)Network Programming

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 <netdb.h>
// 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 <sys/types.h>
#include <sys/socket.h>
//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 <sys/socket.h>
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 <sys/socket.h>
//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 <stdio.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>

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 <port>\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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值