1.全球IP因特網(wǎng)
1.1數(shù)據(jù)在互聯(lián)網(wǎng)上的傳輸過程
1.2 一個網(wǎng)絡程序的軟硬件組織
1.3 IP地址結構
- 一個IP地址就是一個無符號32位整數(shù)。網(wǎng)絡程序將其存放在如下所示結構體中:
struct in_addr{
uint32_t s_addr; //大端法表示的IP地址
};
- 因為網(wǎng)絡字節(jié)序都是大端法表示的获三,所以Unix提供了一組函數(shù)用于在網(wǎng)絡和主機間進行字節(jié)序的轉換:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort); //返回網(wǎng)絡字節(jié)序
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort); //返回主機字節(jié)序
-
IP地址通常以點分十進制表示法來表示的寸五。例如127.0.0.1就是0x7f000001梳凛。Unix也提供了一組函數(shù)用于在點分十進制和32位十六進制數(shù)進行轉換的函數(shù):
#include <arpa/inet.h> int inet_pton(AF_INET,const char *src, void *dst); //成功返回1梳杏,src非法返回0韧拒,出錯返回-1 const char *inet_ntop(AF_INET淹接,const void *src, char *dst, socklen_t size); //成功返回指向字符串的指針,出錯返回NULL
參數(shù)AF_INET代表IPv4叛溢,而若是128位的IPv6地址蹈集,則是AF_INET6;
對于inet_ntop函數(shù)雇初,成功時dst返回指向字符串表示的IP地址的前size字節(jié)拢肆。
1.4因特網(wǎng)域名
為了便于人類的記憶,DNS服務器記錄了所有IP地址和主機名的映射靖诗,我們可以通過Linux系統(tǒng)下的nslookup程序來查看域名對應的地址郭怪。
- 默認的本地主機域名localhost總是映射為回送地址(loopback address )127.0.0.1
linux> nslookup localhost
Address:127.0.0.1
命令行下輸入:
hostname
會得到本機的IP地址。
- 在通常情況下刊橘,多個域名可以映射到同一個或同一組IP地址鄙才;
1.5 地址轉換的示例
- 編寫的函數(shù)hex2dd.c會將它的十六進制參數(shù)轉換為點分十進制串:
#include <stdio.h>
#include <sys/inet.h>
#include "csapp.h"
int main(int argc, char **argv)
{
struct in_addr inaddr;
uint32_t addr;
char buf[MAXBUF];
if(argc != 2){
fprintf(stderr, "usage:%s <hex num>\n",argv[0]);
exit(0);
}
sscanf(argv[1],"%x",&addr);
inaddr.s_addr = htonl(addr);
if(!inet_ntop(AF_INET,&inaddr,buf,MAXBUF))
unix_error("inet_ntop");
printf("%s\n",buf);
exit(0);
}
-
編寫的函數(shù)dd2hex.c會將它的點分十進制串參數(shù)轉換為十六進制:
#include <stdio.h> #include <sys/inet.h> #include "csapp.h" int main(int argc, char **argv) { struct in_addr inaddr; int rc; if(argc != 2){ fprintf(stderr, "usage:%s <dotted-decimal>\n",argv[0]); exit(0); } rc = inet_pton(AF_INET, argv[1], &inaddr); if(rc == 0) app_error("inet_pton error:invalid dotted-decimal address.\n"); elseif(rc < 0) unix_error("inet_pton error.\n"); printf("0x%x\n",ntohl(inaddr.s_addr)); exit(0); }
2.套接字接口
所謂套接字接口其實是一組函數(shù),它們和I/O函數(shù)結合起來促绵,用以創(chuàng)建網(wǎng)絡應用攒庵。下圖是基于套接口的網(wǎng)絡應用概述:
2.1 套接字地址結構
從程序的角度看,套接字就是一個打開文件的描述符败晴。套接字地址存放在類型為sockaddr_in的16字節(jié)結構體中浓冒。
/*IP套接字地址結構,_in是互聯(lián)網(wǎng)的縮寫*/
struct sockaddr_in{
uint16_t sin_family; //協(xié)議簇(AF_INET或AF_INET6)
uint16_t sin_port; //端口號
struct in_addr sin_addr; //IP地址
unsigned char sin_zero[8];//為了與struct sockaddr邊界對齊而填充的字節(jié)
}尖坤;
/*通用套接字地址結構*/
struct sockaddr{
uint16_t sa_family; //協(xié)議簇(AF_INET或AF_INET6)
char sa_data[14];//地址數(shù)據(jù)
}稳懒;
為什么會出現(xiàn)兩種套接字地址?
網(wǎng)絡編程函數(shù)connect慢味、bind和accept要求一個指向與協(xié)議有關的套接字地址結構的指針场梆。但套接字接口設計者面臨的問題是如何定義這些函數(shù),使之能夠接受各種類型的套接字地址結構纯路。而當時void *指針還沒有發(fā)明或油,所以解決辦法是設計的套接字函數(shù)都采用通用地址結構作為參數(shù),而所有特定協(xié)議的套接字指針在使用時都強制轉換成通用結構驰唬。
為了簡化代碼顶岸,Steven指導定義:
typedef struct sockaddr SA;
然后,無論何時需要將sockaddr_in結構強制轉換成sockaddr結構時定嗓,我們都使用(SA)蜕琴。
2.2 socket
該函數(shù)創(chuàng)建一個套接字描述符;
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol); //成功返回非負描述符宵溅,錯誤返回-1
domain: 使用哪種類型的IP地址,如AF_INET上炎。
type :套接字工作類型恃逻,如SOCK_STREAM代表套接字是連接的一個端點雏搂。
protocol: 一般取0
最好的方法是利用getaddrinfo函數(shù)自動生成這些參數(shù),以后會說寇损。這里函數(shù)返回的套接字描述符僅是部分打開的凸郑,還不能用于讀和寫。
2.3 connect
客戶端利用該函數(shù)建立與服務器的連接矛市。
#include <sys/socket.h>
int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen);
//成功返回0芙沥,錯誤返回-1
- clientfd: 客戶端套接字描述符;
- addr: 通用套接字地址
- addrlen: 套接字地址長度浊吏,一般取sizeof(sockaddr_in)
該函數(shù)阻塞等待與服務器的連接而昨,若成功就表示套接字描述符現(xiàn)在可以讀寫了,并且得到的連接是由(客戶端IP地址:客戶端分配的臨時端口號)唯一表示找田。最好的方法也是利用getaddrinfo函數(shù)自動生成這些參數(shù)歌憨。
2.4 bind
服務器用它將套接字地址和套接字描述符綁定起來。
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//成功返回0墩衙,錯誤返回-1
- sockfd: 服務器套接字描述符务嫡;
- addr: 通用套接字地址
- addrlen: 套接字地址長度,一般取sizeof(sockaddr_in)
最好的方法也是利用getaddrinfo函數(shù)自動生成這些參數(shù)漆改。
2.5 listen
服務器調(diào)用該函數(shù)告訴內(nèi)核心铃,該描述符是被服務器用來監(jiān)聽來自客服端連接請求的。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
//成功返回0挫剑,錯誤返回-1
- sockfd: 需要轉化為監(jiān)聽套接字的描述符
- backlog: 內(nèi)核在開始拒絕連接請求之前于个,隊列中要排隊的未完成的連接請求的數(shù)量。一般設為一個較大的值暮顺,比如1024厅篓。
2.6 accept
服務器調(diào)用該函數(shù)等待來自客服端的連接請求。
#include <sys/socket.h>
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
//成功返回非負描述符捶码,錯誤返回-1
該函數(shù)等待來自客戶端的連接請求到達監(jiān)聽描述符listenfd羽氮,然后在addr中填寫客服端的套接字地址,并返回一個已連接描述符惫恼,而它可以用來與客戶端通信档押。
監(jiān)聽描述符和已連接描述符的區(qū)別:
- 監(jiān)聽描述符作為客戶端連接請求的一個端點,通常被創(chuàng)建一次祈纯,存在于服務器的整個生命周期令宿;
- 已連接描述符是客戶端與服務器之間已經(jīng)建立起來的連接的一個端點,服務器每次接受連接請求時都會創(chuàng)建一次腕窥,它只存在與服務器為一個客戶端服務的過程中粒没。
3.轉換函數(shù)
3.1 getaddrinfo
函數(shù)將主機名(網(wǎng)址或點分十進制IP地址)和服務名(端口號)的字符串轉化成套接字地址結構。它是可重入和協(xié)議無關的簇爆,是代替gethostbyname和getservbyname函數(shù)的替代品癞松。
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
struct addrinfo{
int ai_flags; /*hints的參數(shù)*/
int ai_family; /*套接字函數(shù)的第一個參數(shù)*/
int ai_socktype;/*套接字函數(shù)的第二個參數(shù)*/
int ai_protocol;/*套接字函數(shù)的第三個參數(shù)*/
char *ai_canonname;/*主機的官方名*/
size_t ai_addrlen; /*套接字地址長度*/
struct sockaddr *ai_addr; /*套接字地址指針*/
struct addrinfo *ai_next; /*指向下一個addrinfo條目*/
};
int getaddrinfo( const char *host, const char *service,
const struct addrinfo *hints,
struct addrinfo **result); //成功返回0爽撒,錯誤返回非零錯誤代碼。
void freeaddrinfo(struct addrinfo *result); //無返回
const char *gai_strerror(int errcode); //返回錯誤消息字符串
host:可以是域名响蓉,也可以是點分十進制IP地址硕勿。
service :可以是服務名(http等),也可是十進制端口號枫甲。如果不想把主機名或端口號轉換成套接字地址源武,就把相應的參數(shù)設置為NULL,但是至少要有一個有效想幻。
-
hints :控制參數(shù)粱栖,它是一個特定的addrinfo指針,通過對該addrinfo結構體ai_family举畸、ai_socktype查排、ai_protocol、ai_flags成員的設置抄沮,可以控制getaddrinfo函數(shù)返回的套接字地址列表的特性跋核。但其余成員必須設置為0。實際中一般先用memset函數(shù)將整個結構清零叛买,然后又選擇的對某些成員賦值砂代。
- ai_family: 可以設置為AF_INET或AF_INET6。前者表示只返回IPv4的地址率挣,后者返回IPv6的地址刻伊;
- ai_socktype :對host關聯(lián)的每個地址,該函數(shù)默認最多返回3個addrinfo結構椒功,每個的ai_socktype字段不同:“連接”捶箱、“數(shù)據(jù)報”和“原始套接字”。如果ai_socktype被設置為SOCK_STREAM动漾,則將限制為對每個地址只返回1個連接的addrinfo結構丁屎。
-
ai_flags : 它是一個位掩碼,可以進一步修改默認行為,其取值可以是以下宏的
- AI_ADDRCONFIG: 要求只有本地主機被配置為IPv4時旱眯,getaddrinfo返回IPv4地址晨川。
- AI_CANONNAME: 將列表中第一個addrinfo結構的ai_canonname字段指向host的官方名字。
- AI_NUMERICSERV:強制getaddrinfo的service參數(shù)為端口號删豺。
- AI_PASSIVE: getaddrinfo默認返回用于客戶端的主動套接字共虑,如果設置了該標志,那么函數(shù)會返回用于服務器的被動套接字呀页,此時host設置為NULL妈拌。得到的套接字地址結構中的地址字段sa_data[14]是通配符地址(wildcard address),表示這個服務器會接受所有發(fā)送到服務器的IP地址的請求赔桌。
result : 指向一個包含sockaddr(套接字地址結構)地址的addrinfo結構體鏈表供炎。一般調(diào)用完這個函數(shù)之后渴逻,會遍歷該鏈表疾党,依次嘗試每個套接字地址音诫,直到socket和connect或bind連接成功。
getaddrinfo函數(shù)返回的addrinfo結構中的ai_addr指向的套接字地址可以直接用來傳遞給套接字接口中的函數(shù)(socket雪位、connect竭钝、bind踊兜、listen蛹屿、accept等)暂殖,該特點使得我們編寫的客戶端和服務器能夠獨立于某個特殊版本的IP協(xié)議悍及。下圖展示了getaddrinfo返回的數(shù)據(jù)結構:
- 為了避免內(nèi)存泄漏芬萍,一般在調(diào)用完getaddrinfo函數(shù)之后蹄梢,會調(diào)用freeaddrinfo函數(shù)釋放該鏈表傍睹;
- 如果getaddrinfo遇到錯誤搀菩,應用程序可以調(diào)用gai_strerror函數(shù)將錯誤代碼轉換成字符串螃成。
- 當getaddrinfo創(chuàng)建列表中的addrinfo結構時旦签,會填寫除了ai_flags的每個字段。
3.2 getnameinfo
函數(shù)將一個套接字地址結構轉化成相應的主機名(網(wǎng)址或點分十進制IP地址)和服務名(端口號)字符串寸宏,并將它們復制到host和service緩沖區(qū)宁炫。它也是可重入和協(xié)議無關的,是代替gethostbyaddr和getservbyport函數(shù)的替代品氮凝。
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo( const struct sockaddr *sa, socklen_t salen,
char *host, size_t hostlen,
char *service, size_t servlen, int flags);
//成功返回0羔巢,錯誤返回非零錯誤代碼。
sa :指向大小為salen字節(jié)的套接字地址結構罩阵;
host :指向大小為hostlen字節(jié)的緩沖區(qū)竿秆;如果不想要主機名,可以設置為NULL稿壁,hostlen設置為0幽钢。
service :指向大小為servlen字節(jié)的緩沖區(qū);如果不想要服務名常摧,也可以設置為NULL搅吁,servlen設置為0,但二者不能同時都設為NULL落午。
-
flags :它是一個位掩碼谎懦,可以進一步修改默認行為,其取值可以是以下宏的或。
- NI_NUMERICHOST: getnameinfo函數(shù)默認返回域名溃斋,若設置此項則返回數(shù)字地址界拦。
- NI_NUMERICSERV: 默認返回服務名(如果可能的話),若設置此項則返回端口號梗劫。
3.3 域名翻譯程序
利用上兩節(jié)所學的兩個函數(shù)享甸,編寫程序hostinfo.c截碴,當輸入一個域名時,會得到相應的點分十進制IPv4地址蛉威。
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int main(int argc, char **argv)
{
struct addrinfo *p, *listp, hints;
char buf[MAXLINE];
int rc, flags;
if (argc != 2) {
fprintf(stderr, "usage: %s <domain name>\n", argv[0]);
exit(0);
}
/* Get a list of addrinfo records */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET; /* IPv4 only */ //line:netp:hostinfo:family
hints.ai_socktype = SOCK_STREAM; /* Connections only */ //line:netp:hostinfo:socktype
if ((rc = getaddrinfo(argv[1], NULL, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(rc));
exit(1);
}
/* Walk the list and display each IP address */
flags = NI_NUMERICHOST; /* Display address string instead of domain name */
for (p = listp; p; p = p->ai_next) {
Getnameinfo(p->ai_addr, p->ai_addrlen, buf, MAXLINE, NULL, 0, flags);
printf("%s\n", buf);
}
/* Clean up */
Freeaddrinfo(listp);
exit(0);
}
首先日丹,初始化hints結構,使getaddrinfo返回我們想要的地址蚯嫌。我們想得到用作“連接”的IPv4地址哲虾,且只想得到域名,不要服務名束凑。
然后,遍歷addrinfo結構鏈表栅盲,用getnameinfo將每個套接字地址轉換成IPv4地址字符串汪诉。
最后,用freeaddrinfo函數(shù)釋放鏈表谈秫。
運行程序扒寄,我們會看到twitter.com映射到4個IP地址。
linux> ./hostinfo twitter.com
199.16.156.102
199.16.156.230
199.16.156.6
199.16.156.70
4. 套接字接口的輔助函數(shù)
套接字接口函數(shù)和轉換函數(shù)看上去有些可怕孝常,相當復雜凌亂旗们。這一節(jié)會介紹一些包裝函數(shù),它們將會大大簡化客戶端和服務器通信程序的編寫构灸。
4.1 open_clientfd
客戶端可以直接利用該函數(shù)建立與服務器的連接。
#include "csapp.h"
int open_clientfd(char * hostname, char *port);
//成功返回套接字描述符喜颁,出錯返回-1
下面是它的源代碼,它是可重入和協(xié)議無關的半开。
int open_clientfd(char *hostname, char *port) {
int clientfd, rc;
struct addrinfo hints, *listp, *p;
/* Get a list of potential server addresses */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM; /* Open a connection */
hints.ai_flags = AI_NUMERICSERV; /* ... using a numeric port arg. */
hints.ai_flags |= AI_ADDRCONFIG; /* Recommended for connections */
if ((rc = getaddrinfo(hostname, port, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo failed (%s:%s): %s\n", hostname, port, gai_strerror(rc));
return -2;
}
/* Walk the list for one that we can successfully connect to */
for (p = listp; p; p = p->ai_next) {
/* Create a socket descriptor */
if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue; /* Socket failed, try the next */
/* Connect to the server */
if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1)
break; /* Success */
if (close(clientfd) < 0) { /* Connect failed, try another */ //line:netp:openclientfd:closefd
fprintf(stderr, "open_clientfd: close failed: %s\n", strerror(errno));
return -1;
}
}
/* Clean up */
freeaddrinfo(listp);
if (!p) /* All connects failed */
return -1;
else /* The last connect succeeded */
return clientfd;
}
假設服務器運行在主機hostname上寂拆,并在端口port上監(jiān)聽連接請求。
首先纠永,調(diào)用getaddrinfo,返回一個addrinfo結構體鏈表尝江。遍歷該列表依次嘗試列表中的每個條目中的ai_addr指向的套接字地址,直到調(diào)用socket和connect成功。如果一個失敗苍日,則在下一次嘗試前關閉掉這個套接字描述符。如果成功建立連接相恃,就釋放列表內(nèi)存,并把套接字描述符返回給客戶端嫌佑,客戶端就可以利用它與所有Unix I/O函數(shù)與服務器通信了豆茫。
4.2 open_listenfd
服務器可以直接利用該函數(shù)創(chuàng)建一個監(jiān)聽描述符侨歉,準備好建立與客戶端的連接屋摇。
#include "csapp.h"
int open_listenfd(char *port);
//成功返回套接字描述符,出錯返回-1
open_listenfd函數(shù)返回一個打開的監(jiān)聽描述符幽邓,且已經(jīng)準備好在端口port上接受客戶端的連接請求炮温。下面是它的源代碼:
int open_listenfd(char *port)
{
struct addrinfo hints, *listp, *p;
int listenfd, rc, optval=1;
/* Get a list of potential server addresses */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM; /* Accept connections */
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* ... on any IP address */
hints.ai_flags |= AI_NUMERICSERV; /* ... using port number */
if ((rc = getaddrinfo(NULL, port, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo failed (port %s): %s\n", port, gai_strerror(rc));
return -2;
}
/* Walk the list for one that we can bind to */
for (p = listp; p; p = p->ai_next) {
/* Create a socket descriptor */
if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue; /* Socket failed, try the next */
/* Eliminates "Address already in use" error from bind */
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, //line:netp:csapp:setsockopt
(const void *)&optval , sizeof(int));
/* Bind the descriptor to the address */
if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
break; /* Success */
if (close(listenfd) < 0) { /* Bind failed, try the next */
fprintf(stderr, "open_listenfd close failed: %s\n", strerror(errno));
return -1;
}
}
/* Clean up */
freeaddrinfo(listp);
if (!p) /* No address worked */
return -1;
/* Make it a listening socket ready to accept connection requests */
if (listen(listenfd, LISTENQ) < 0) {
close(listenfd);
return -1;
}
return listenfd;
}
首先,調(diào)用getaddrinfo牵舵,返回一個addrinfo結構體鏈表柒啤。遍歷該列表依次嘗試列表中的每個條目中的ai_addr指向的套接字地址,直到調(diào)用socket和bind成功畸颅。如果失敗担巩,則在下一次嘗試前關閉掉這個套接字描述符。如果成功綁定没炒,就釋放列表內(nèi)存涛癌,并調(diào)用listen函數(shù)將該套接字描述符轉換為監(jiān)聽描述符返回給調(diào)用者,服務器就可以利用它與所有Unix I/O函數(shù)響應客戶端了送火。
- 我們使用了setsockopt函數(shù)來配置服務器拳话,使得服務器能夠被終止、重啟和立即接受連接种吸。一個重啟的服務器默認將在30秒內(nèi)拒絕客戶端的連接請求弃衍。關于setsockopt的使用很復雜,將會專門寫篇文章來講解他的使用方法坚俗。
- 我們使用了AI_PASSIVE標志并將host參數(shù)設置為NULL镜盯,這樣每個套接字地址字段都會被設置為通配符地址,表示服務器接受發(fā)送到本機所有IP地址的請求猖败。
4.3 編寫客戶端echo和服務器程序
4.3.1客戶端程序echoclient
客戶端首先與服務器建立連接速缆,之后進入循環(huán)等待從標準輸入讀取文本行發(fā)送給服務器。再等待從服務器取回回送的行辙浑,并輸出結果到標準輸出激涤。
#include "csapp.h"
int main(int argc, char **argv)
{
int clientfd;
char *host, *port, buf[MAXLINE];
rio_t rio;
if (argc != 3) {
fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
exit(0);
}
host = argv[1];
port = argv[2];
clientfd = Open_clientfd(host, port);
Rio_readinitb(&rio, clientfd);
while (Fgets(buf, MAXLINE, stdin) != NULL) {
Rio_writen(clientfd, buf, strlen(buf));
Rio_readlineb(&rio, buf, MAXLINE);
Fputs(buf, stdout);
}
Close(clientfd); //line:netp:echoclient:close
exit(0);
}
- 當fgets遇到EOF或鍵盤上鍵入Ctrl + D,或重定向到標準輸入的文件中用盡了所有文本行時,循環(huán)就終止倦踢。
- 循環(huán)終止后,客戶端關閉描述符犁嗅。此時會導致發(fā)送一個EOF到服務器褂微。
4.3.2 服務器程序echoserver
服務器首先打開監(jiān)聽描述符园爷,進入循環(huán)等待與客戶端建立連接童社,連接之后首先輸出客戶端的域名和IP扰楼,之后調(diào)用echo函數(shù)為其服務。echo函數(shù)返回后關閉已連接的描述符项栏,連接終止沼沈。
#include "csapp.h"
void echo(int connfd);
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr; /* Enough space for any address */ //line:netp:echoserveri:sockaddrstorage
char client_hostname[MAXLINE], client_port[MAXLINE];
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(0);
}
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(struct sockaddr_storage);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
Getnameinfo((SA *) &clientaddr, clientlen, client_hostname, MAXLINE,
client_port, MAXLINE, 0);
printf("Connected to (%s, %s)\n", client_hostname, client_port);
echo(connfd);
Close(connfd);
}
exit(0);
}
void echo(int connfd)
{
size_t n;
char buf[MAXLINE];
rio_t rio;
Rio_readinitb(&rio, connfd);
while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) { //line:netp:echo:eof
printf("server received %d bytes\n", (int)n);
Rio_writen(connfd, buf, n);
}
}
- clientaddr是一個套接字地址結構庆冕,被傳遞給accept函數(shù),在accept返回前會將連接到的客戶端套接字地址填入到clientaddr拷姿。
- 將clientaddr聲明為struct sockaddr_storage是因為該結構足夠大能裝下任何類型的套接字地址旱函,以保持代碼的協(xié)議無關性棒妨。
- 我們這里建立的echo服務器一次只能處理一個客戶端連接。需要不停的在多個客戶端間迭代服務拘泞,也稱之為迭代服務器陪腌。
- echo函數(shù)反復讀寫文本行烟瞧,直到rio_readlineb函數(shù)遇到EOF参滴。
獲取更多知識卵洗,請點擊關注:
嵌入式Linux&ARM
CSDN博客
簡書博客
知乎專欄