UNIX网络编程笔记(2):一个简单的时间获取程序
這一講通過一個簡單的時間獲取程序簡單介紹套接字編程。
1、套接字API
1.1、套接字地址結構
上一講中介紹了TCP的一些內容,知道了一個套接字對唯一標識了網絡中的一個TCP連接,而一個套接字標識了一個TCP連接中的一端。套接字中包含本地IP地址和本地端口,由于現在的IP地址有IPv4和IPv6,因此套接字地址結構也就有兩種。
1.1.1、IPv4套接字地址結構
IPv4套接字地址結構也叫“網際套接字地址結構”,包含在<netinet/in.h>頭文件中,下面是它的定義:
<pre name="code" class="cpp">struct sockaddr_in {uint_8 sin_len;//地址長度sa_family_t sin_family;//協議族:在這里是AF_INETin_port_in sin_port;//16位端口號:網絡字節序struct in_addrsin_addr;//32位IP地址結構:網絡字節序char sin_zero[8];//未使用 }; struct in_addr {in_addr_t s_addr;//32位IP地址:網絡字節序 };可以知道,IPv4套接字地址結構的大小是16字節。注意:套接字地址結構只在主機上使用,雖然一些字段(比如IP地址和端口號)可以在不同主機間傳遞,但結構本身并不傳遞。
1.1.2、通用的套接字地址結構
當套接字作為參數傳遞進套接字函數時,套接字是作為引用的形式來傳遞的。下面是一個通用套接字地址結構,定義在<sys/socket.h>頭文件中:
struct sockaddr {uint8_t sa_len;//地址長度sa_family_t sa_family;//協議族char sa_data;//數據:包括IP地址和端口號 };這個結構的唯一作用就是對指向特定于協議的套接字地址結構的指針執行類型強制轉換。
1.1.3、IPv6套接字地址結構
IPv4套接字地址結構也定義在<netinet/in.h>頭文件中。
struct in6_addr {uint8_t s6_addr[16];//128位IPv6地址:網絡字節序 };#define SIN6_LENstruct sockaddr_in6 {uint8_t sin6_len;//地址長度sa_family_t sin6_family;//協議族:在這里是AF_INET6in_port_t sin6_port;//端口號:網絡字節序uint32_t sin6_flowinfo;//未定義structin6_addr sin6_addr;//IPv6地址結構uint32_t sin6_scope_id;//地址范圍 };1.1.4、新的通用套接字地址結構
當需要用一個地址結構應用兩種不同的IP地址時,這個新的通用套接字地址結構克服了sockaddr的缺點。定義在<netinet/in.h>頭文件中:
struct sockaddr_storage {uint8_t ss_len;//地址長度sa_family_tss_family;//協議族 };sockaddr_storage能夠滿足任何對齊要求;
sockaddr_storage足夠大,能容納任何一種套接字地址結構;
除了ss_len和ss_family外,其它數據對用戶透明。
1.2、值-結果參數
C 語言中,函數的返回值只能有一個,不過可以通過傳遞引用參數來達到返回多個結果的效果。C++中也有按引用傳遞參數,引用其實就是指針,由于實參與形參指 向同一塊地址,因此在函數中對形參的修改也會反映到實參中,這樣就達到了返回通過引用返回結果的效果。值-結果參數也是這樣。在網絡編程中,套接字地址結 構通常是值-結果參數。
當向套接字函數傳遞套接字地址結構時,總是以引用的形式來傳遞,作為參數,套接字地址結構中的地址長度告訴內核需要從進程中復制多少數據,避免了越界訪問。如下圖:
當函數返回時,內核將地址的真實大小放入長度中,因此長度又作為結果從內核中返回到進程中,告訴了進程這個結構究竟存儲了多少數據。這種類型的參數叫做值-結果參數。如下圖:
1.3、網絡字節序與主機字節序
對于多字節數據,在內存中的存數方式有兩種:小端字節序和大端字節序。
小端字節序:將低序字節存在起始地址;
大端字節序:將高序字節存在其實地址;
下圖展示了兩種格式:
舉例來說:對于數字1,小端字節序為:
| 0 | 0 | 0 | 1 |
A+3????????A+2????????A+1????????? A
即,0x0001
而大端字節序為:
| 1 | 0 | 0 | 0 |
A+3????????A+2????????A+1???????? A
如果不轉換的話,那么這個數就是0x1000
網絡字節序使用大端字節序。所以需要在主機字節序和網絡字節序間轉換。下面是轉換函數,定義在<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);前兩個函數將主機字節序轉換成網絡字節序;后兩個函數將網絡字節序轉換成主機字節序。
1.4、地址轉換函數
下面的函數都在<arpa/inet.h>頭文件中。
(1)inet_aton函數
函數將點分十進制地址轉換成IPv4地址。定義如下:
int inet_aton(const char *strptr,struct in_addr*addrptr);如果字符串有效就返回1,無效返回0.地址存在addrptr中,以網絡字節序存儲。
(2)inet_addr函數
函數將點分十進制地址轉換成IPv4地址。定義如下:
in_addr_t inet_addr(const char *strptr);如果有效返回IPv4地址,否則返回INADDR_NONE。函數已經被廢棄。
(3)inet_ntoa函數
函數將IPv4地址轉換成點分十進制地址字符串。定義如下:
cahr *inet_ntoa(struct in_addr inaddr);參數的地址是網絡字節序。
以下兩個函數對IPv4和IPv6地址都適用。
(4)inet_pton函數
函數將IP地址轉換成字符串。定義如下:
int inet_pton(int family,const char *strptr,void *addrptr);如果成功返回1,輸入無效返回0,出錯返回-1。(5)inet_ntop函數
函數與inet_pton函數操作相反。定義如下:
2、套接字函數
2.1、函數
套接字函數有下面幾個,定義在<sys/socket.h>頭文件中。 int socket(int family,int type,int protocol);//返回:成功返回非負描述符,出錯返回-1 int connect(int sockfd,const sockaddr *servaddr,socklen_t addrlen);//返回:成功返回0,出錯返回-1 int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);//返回:成功返回0,出錯返回-1 int listen(int sockfd,int backlog);//返回:成功返回0,出錯返回-1 int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);//返回:成功返回非負描述符,出錯返回 int close(int sockfd);//返回:成功返回0,出錯返回2.2、套接字函數的調用過程
下面是一個完整的TCP連接中服務器與客戶的函數調用過程:3、一個簡單的時間獲取程序
下面的程序運用了上面講述的內容,分為客戶端程序和服務器端程序。代碼如下: (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)服務器端 #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;}} }運行結果如下: (1)啟動服務器程序開始監聽。 (2)啟動客戶端
得到結果 (3)服務器端
總結
以上是生活随笔為你收集整理的UNIX网络编程笔记(2):一个简单的时间获取程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 明天我要和班上同学solo 我用阿卡丽他
- 下一篇: 有谁做人工授精成功的