hub会把收到的frame发向所有port,但是bridge会通过学习,把数据只发向目的端口(或丢弃)
网络上一般采用big-endian传输数据。
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);
从客户端向服务端连接时先使用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;
}
服务端一般使用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;
}
上面对于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);
}
}
在我们调试网络服务时,可以通过telnet指令来模拟客户端
在http header里有Host这一项是为了方便代理服务器来寻找自己是否有缓存使用
对于静态资源,server直接返回即可,对于动态资源则会使用Common Gateway Interface来处理。这个时候server会fork一个进程,使用dup2来重定向standard output(因为这是cgi输出的地方)通过设置环境变量(如下表),然后调用execve来执行*/cgi-bin/adder来处理,而cgi会通过getenv*来获取设置的环境变量,这就是CGI标准.
而作为cgi程序除了需要返回content外还需要返回content-type,content-length等信息。可以实现使用sprintf来存好数据,再使用printf来写入stantard out,最后使用fflush刷缓存
文中给出了一个示例,但是也指出对于一个真正的server而言需要处理各种意外情况,比如客户端断开链接而导致的SIGPIPE