UNIX网络编程笔记(2):一个简单的时间获取程序
這一講通過一個(gè)簡單的時(shí)間獲取程序簡單介紹套接字編程。
1、套接字API
1.1、套接字地址結(jié)構(gòu)
上一講中介紹了TCP的一些內(nèi)容,知道了一個(gè)套接字對唯一標(biāo)識了網(wǎng)絡(luò)中的一個(gè)TCP連接,而一個(gè)套接字標(biāo)識了一個(gè)TCP連接中的一端。套接字中包含本地IP地址和本地端口,由于現(xiàn)在的IP地址有IPv4和IPv6,因此套接字地址結(jié)構(gòu)也就有兩種。
1.1.1、IPv4套接字地址結(jié)構(gòu)
IPv4套接字地址結(jié)構(gòu)也叫“網(wǎng)際套接字地址結(jié)構(gòu)”,包含在<netinet/in.h>頭文件中,下面是它的定義:
<pre name="code" class="cpp">struct sockaddr_in {uint_8 sin_len;//地址長度sa_family_t sin_family;//協(xié)議族:在這里是AF_INETin_port_in sin_port;//16位端口號:網(wǎng)絡(luò)字節(jié)序struct in_addrsin_addr;//32位IP地址結(jié)構(gòu):網(wǎng)絡(luò)字節(jié)序char sin_zero[8];//未使用 }; struct in_addr {in_addr_t s_addr;//32位IP地址:網(wǎng)絡(luò)字節(jié)序 };可以知道,IPv4套接字地址結(jié)構(gòu)的大小是16字節(jié)。注意:套接字地址結(jié)構(gòu)只在主機(jī)上使用,雖然一些字段(比如IP地址和端口號)可以在不同主機(jī)間傳遞,但結(jié)構(gòu)本身并不傳遞。
1.1.2、通用的套接字地址結(jié)構(gòu)
當(dāng)套接字作為參數(shù)傳遞進(jìn)套接字函數(shù)時(shí),套接字是作為引用的形式來傳遞的。下面是一個(gè)通用套接字地址結(jié)構(gòu),定義在<sys/socket.h>頭文件中:
struct sockaddr {uint8_t sa_len;//地址長度sa_family_t sa_family;//協(xié)議族char sa_data;//數(shù)據(jù):包括IP地址和端口號 };這個(gè)結(jié)構(gòu)的唯一作用就是對指向特定于協(xié)議的套接字地址結(jié)構(gòu)的指針執(zhí)行類型強(qiáng)制轉(zhuǎn)換。
1.1.3、IPv6套接字地址結(jié)構(gòu)
IPv4套接字地址結(jié)構(gòu)也定義在<netinet/in.h>頭文件中。
struct in6_addr {uint8_t s6_addr[16];//128位IPv6地址:網(wǎng)絡(luò)字節(jié)序 };#define SIN6_LENstruct sockaddr_in6 {uint8_t sin6_len;//地址長度sa_family_t sin6_family;//協(xié)議族:在這里是AF_INET6in_port_t sin6_port;//端口號:網(wǎng)絡(luò)字節(jié)序uint32_t sin6_flowinfo;//未定義structin6_addr sin6_addr;//IPv6地址結(jié)構(gòu)uint32_t sin6_scope_id;//地址范圍 };1.1.4、新的通用套接字地址結(jié)構(gòu)
當(dāng)需要用一個(gè)地址結(jié)構(gòu)應(yīng)用兩種不同的IP地址時(shí),這個(gè)新的通用套接字地址結(jié)構(gòu)克服了sockaddr的缺點(diǎn)。定義在<netinet/in.h>頭文件中:
struct sockaddr_storage {uint8_t ss_len;//地址長度sa_family_tss_family;//協(xié)議族 };sockaddr_storage能夠滿足任何對齊要求;
sockaddr_storage足夠大,能容納任何一種套接字地址結(jié)構(gòu);
除了ss_len和ss_family外,其它數(shù)據(jù)對用戶透明。
1.2、值-結(jié)果參數(shù)
C 語言中,函數(shù)的返回值只能有一個(gè),不過可以通過傳遞引用參數(shù)來達(dá)到返回多個(gè)結(jié)果的效果。C++中也有按引用傳遞參數(shù),引用其實(shí)就是指針,由于實(shí)參與形參指 向同一塊地址,因此在函數(shù)中對形參的修改也會(huì)反映到實(shí)參中,這樣就達(dá)到了返回通過引用返回結(jié)果的效果。值-結(jié)果參數(shù)也是這樣。在網(wǎng)絡(luò)編程中,套接字地址結(jié) 構(gòu)通常是值-結(jié)果參數(shù)。
當(dāng)向套接字函數(shù)傳遞套接字地址結(jié)構(gòu)時(shí),總是以引用的形式來傳遞,作為參數(shù),套接字地址結(jié)構(gòu)中的地址長度告訴內(nèi)核需要從進(jìn)程中復(fù)制多少數(shù)據(jù),避免了越界訪問。如下圖:
當(dāng)函數(shù)返回時(shí),內(nèi)核將地址的真實(shí)大小放入長度中,因此長度又作為結(jié)果從內(nèi)核中返回到進(jìn)程中,告訴了進(jìn)程這個(gè)結(jié)構(gòu)究竟存儲了多少數(shù)據(jù)。這種類型的參數(shù)叫做值-結(jié)果參數(shù)。如下圖:
1.3、網(wǎng)絡(luò)字節(jié)序與主機(jī)字節(jié)序
對于多字節(jié)數(shù)據(jù),在內(nèi)存中的存數(shù)方式有兩種:小端字節(jié)序和大端字節(jié)序。
小端字節(jié)序:將低序字節(jié)存在起始地址;
大端字節(jié)序:將高序字節(jié)存在其實(shí)地址;
下圖展示了兩種格式:
舉例來說:對于數(shù)字1,小端字節(jié)序?yàn)?
| 0 | 0 | 0 | 1 |
A+3????????A+2????????A+1????????? A
即,0x0001
而大端字節(jié)序?yàn)?#xff1a;
| 1 | 0 | 0 | 0 |
A+3????????A+2????????A+1???????? A
如果不轉(zhuǎn)換的話,那么這個(gè)數(shù)就是0x1000
網(wǎng)絡(luò)字節(jié)序使用大端字節(jié)序。所以需要在主機(jī)字節(jié)序和網(wǎng)絡(luò)字節(jié)序間轉(zhuǎn)換。下面是轉(zhuǎn)換函數(shù),定義在<netinet/in.h>頭文件中:
uint16_t htons(uint16_t host16bitvalue); uint32_t htonl(uint32_t host32bitvalue); uint16_t ntohs(uint16_t net16bitvalue); uint32_t ntohl(uint32_t net32bitvalue);前兩個(gè)函數(shù)將主機(jī)字節(jié)序轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序;后兩個(gè)函數(shù)將網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換成主機(jī)字節(jié)序。
1.4、地址轉(zhuǎn)換函數(shù)
下面的函數(shù)都在<arpa/inet.h>頭文件中。
(1)inet_aton函數(shù)
函數(shù)將點(diǎn)分十進(jìn)制地址轉(zhuǎn)換成IPv4地址。定義如下:
int inet_aton(const char *strptr,struct in_addr*addrptr);如果字符串有效就返回1,無效返回0.地址存在addrptr中,以網(wǎng)絡(luò)字節(jié)序存儲。
(2)inet_addr函數(shù)
函數(shù)將點(diǎn)分十進(jìn)制地址轉(zhuǎn)換成IPv4地址。定義如下:
in_addr_t inet_addr(const char *strptr);如果有效返回IPv4地址,否則返回INADDR_NONE。函數(shù)已經(jīng)被廢棄。
(3)inet_ntoa函數(shù)
函數(shù)將IPv4地址轉(zhuǎn)換成點(diǎn)分十進(jìn)制地址字符串。定義如下:
cahr *inet_ntoa(struct in_addr inaddr);參數(shù)的地址是網(wǎng)絡(luò)字節(jié)序。
以下兩個(gè)函數(shù)對IPv4和IPv6地址都適用。
(4)inet_pton函數(shù)
函數(shù)將IP地址轉(zhuǎn)換成字符串。定義如下:
int inet_pton(int family,const char *strptr,void *addrptr);如果成功返回1,輸入無效返回0,出錯(cuò)返回-1。(5)inet_ntop函數(shù)
函數(shù)與inet_pton函數(shù)操作相反。定義如下:
2、套接字函數(shù)
2.1、函數(shù)
套接字函數(shù)有下面幾個(gè),定義在<sys/socket.h>頭文件中。 int socket(int family,int type,int protocol);//返回:成功返回非負(fù)描述符,出錯(cuò)返回-1 int connect(int sockfd,const sockaddr *servaddr,socklen_t addrlen);//返回:成功返回0,出錯(cuò)返回-1 int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);//返回:成功返回0,出錯(cuò)返回-1 int listen(int sockfd,int backlog);//返回:成功返回0,出錯(cuò)返回-1 int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);//返回:成功返回非負(fù)描述符,出錯(cuò)返回 int close(int sockfd);//返回:成功返回0,出錯(cuò)返回2.2、套接字函數(shù)的調(diào)用過程
下面是一個(gè)完整的TCP連接中服務(wù)器與客戶的函數(shù)調(diào)用過程:3、一個(gè)簡單的時(shí)間獲取程序
下面的程序運(yùn)用了上面講述的內(nèi)容,分為客戶端程序和服務(wù)器端程序。代碼如下: (1)客戶端 #include <sys/socket.h> #include <stdio.h> #include <string.h> #include <strings.h> #include <netinet/in.h> #include <arpa/inet.h> #define MAXLINE 1024int main(int argc,char *argv[]) {int sockfd;char recvline[MAXLINE];if(argc!=2||strcmp(argv[1],"--help")==0){printf("Usage:%s <IPaddress>\n",argv[0]);return 0;}if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0){printf("socket error\n");return 0;}struct sockaddr_in servaddr;bzero(&servaddr,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(5000);if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr)<=0){printf("inet_pton error for %s\n",argv[1]);return 0;}if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0){printf("connect error\n");return 0;}int n;while((n=read(sockfd,recvline,MAXLINE))>0){recvline[n]=0;if(fputs(recvline,stdout)==EOF){printf("fputs error\n");return 0;}}if(n<0){printf("read error\n");return 0;}return 0; }(2)服務(wù)器端 #include <sys/socket.h> #include <string.h> #include <strings.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <time.h> #define MAXLINE 1024int main(int argc,char *argv[]) {int listenfd;struct sockaddr_in servaddr;char buff[MAXLINE];time_t ticks;if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0){printf("socket error\n");return 0;}bzero(&servaddr,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(5000);servaddr.sin_addr.s_addr=htonl(INADDR_ANY);if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0){printf("bind error\n");return 0;}if(listen(listenfd,5)<0){printf("listen error\n");return 0;}int connfd;socklen_t len;struct sockaddr_in cliaddr;for(;;){len=sizeof(cliaddr);printf("Listening...\n");if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&len))<0){printf("accept error\n");return 0;}printf("Receive a connection from:%s.%d\n",inet_ntop(AF_INET,&cliaddr.sin_addr,buff,sizeof(buff)),ntohs(cliaddr.sin_port));ticks=time(NULL);snprintf(buff,sizeof(buff),"%.24s\r\n",ctime(&ticks));if(write(connfd,buff,strlen(buff))<0){printf("write error\n");return 0;}if(close(connfd)<0){printf("close error\n");return 0;}} }運(yùn)行結(jié)果如下: (1)啟動(dòng)服務(wù)器程序開始監(jiān)聽。 (2)啟動(dòng)客戶端
得到結(jié)果 (3)服務(wù)器端
總結(jié)
以上是生活随笔為你收集整理的UNIX网络编程笔记(2):一个简单的时间获取程序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 明天我要和班上同学solo 我用阿卡丽他
- 下一篇: UNIX网络编程笔记(3):简单的并发服