SOCKET網絡編程——服務器端

3、socket的基本操作
  • socket()函數
  • bind()函數
  • listen()、connect()函數
  • accept()函數
  • read()、write()函數
  • close()函數



if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 )

socket函數

 intsocket(int domain, int type,int protocol);

第一個參數: omain:即協議域,又稱爲協議族(family)。常用的協議族有,AF_INETAF_INET6AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協議族決定了socket的地址類型,在通信中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名作爲地址。

常用的協議族:

(1)、AF_INET:AF_INET(又稱 PF_INET)是 IPv4 網絡協議的套接字類型,選擇 AF_INET 的目的就是使用 IPv4 進行通信。因爲 IPv4 使用 32 位地址。

(2)、AF_INET6:AF_INET6 則是 IPv6 的;

(3)、AF_UNIX:AF_UNIX 則是 Unix 系統本地通信;

第二個參數int type:指定socket類型。常用的socket類有,SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等

(1)、SOCK_STREAM(常用的):SOCK_STREAM套接口(流套接口)的性質

1、不保留任何消息的邊界

      舉一個例子:本地主機通過兩次獨立的write(2)調用向遠程主機發送數據,第一次本地進程寫入25字節的數據,並通過套接口發送到遠程進程,第二次再寫入30字節的數據發往遠程進程,總共55字節的數據,而遠程進程從套接口接收數據時,將消息作爲一個完整的單元來接收,或是通過若干次獨立的讀操作來將數據取走,即接受端並不知道這55字節的數據是分25字節和30字節兩次來發送的。

2、有序性  可以保證接受的數據字節與發送是順序完全一致(意味着通信之前必須建立一個連接)

3、無錯性   可以保證接受的數據在接收端被無錯的接受。如果有錯誤發生,在嘗試完所有的錯誤恢復措施後仍無法消除錯誤,流套接口就會報告錯誤。所進行的錯誤恢復措施嘗試是完全自動的,不需編程者的指導。

(2)、SOCK_DGRAM:

特徵:

1、分組在發送後,可能無序地到達接收端

2、分組可能丟失。如果發生丟失,不會採取任何補救的措施,而且接受端也不必知道有分租丟失。

3、數據報分組有尺寸大小的限制,如果超出限制,在某些路由器和節點上就無法傳送。

4、分組是在不建立連接的情況下被髮送到遠程進程的。

第三個參數protocol:故名思意,就是指定協議。常用有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC等,它們分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議

二、struct sockaddr_in     servaddr結構體賦值



memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(DEFAULT_PORT);

先介紹下這個結構體struct sockaddr_in     servaddr的成員
{
short int sin_family;                     
unsigned short int sin_port;      
struct in_addr sin_addr;            
unsigned char sin_zero[8];       

}
成員介紹
short int sin_family:sin_family指代協議族,在socket編程中只能是AF_INET;
unsigned short int sin_port;sin_port存儲端口號(使用網絡字節順序);
struct in_addr sin_addr:sin_addrs存儲IP地址,一般爲32位的unsigned int,其字節順序爲網絡字節序,即該無符號數採用大端字節序。其中每8位表示一個IP地址中的一個數值。打印的時候可以調用inet_ntoa()函數將其轉換爲char*類型。
unsigned char sin_zero[8]:是爲了讓sockaddr與sockaddr_in兩個數據結構保持大小相同而保留的空字節。s_addr按照網絡字節順序存儲IP地址

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

htonl(INADDR_ANY):INADDR_ANY就是指定地址爲0.0.0.0的地址,這個地址事實上表示不確定地址,或“所有地址”、“任意地址”。 一般來說,在各個系統中均定義成爲0值。
htonl函數:就是把本機字節順序轉化爲網絡字節順序

h---host 本地主機
to  就是to 了
n  ---net 網絡的意思
l 是 unsigned long

ntohs函數:就是把網絡字節順序轉化爲本機字節順序

n——net

h——host

s——unsigned short


(3)        if( bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)

bind函數

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能描述

當用socket()函數創建套接字以後,套接字在名稱空間(網絡地址族)中存在,但沒有任何地址給它賦值。bind()把用addr指定的地址賦值給用文件描述符代表的套接字sockfdaddrlen指定了以addr所指向的地址結構體的字節長度。一般來說,該操作稱爲“給套接字命名”。

通常,在一個SOCK_STREAM套接字接收連接之前,必須通過bind()函數用本地地址爲套接字命名。

備註:調用bind()函數之後,爲socket()函數創建的套接字關聯一個相應地址,發送到這個地址的數據可以通過該套接字讀取與使用。

返回值

成功,返回0;出錯,返回-1,相應地設定全局變量errno。

錯誤

EACCESS

地址空間受保護,用戶不具有超級用戶的權限。

EADDRINUSE

給定的地址正被使用。

 參數說明

第1個參數sockfd:是用socket()函數創建的文件描述符;

第2個參數addr是指向一個結構爲sockaddr參數的指針,sockaddr中包含了地址、端口和IP地址的信息。在進行地址綁定的時候,需要先將地址結構中的IP地址、端口、類型等結構struct sockaddr中的域進行設置之後才能進行綁定,這樣進行綁定後才能將套接字文件描述符與地址等接合在一起。

第3個參數addrlen是addr結構的長度,可以設置成sizeof(struct sockaddr)。使用sizeof(struct sockaddr)來設置套接字的類型和其對應的結構。 

bind的errno值及含義

含義 備註
EADDRINUSE 給定地址已經使用  
EBADF sockfd不合法  
EINVAL sockfd已經綁定到其他地址  
ENOTSOCK sockfd是一個文件描述符,不是socket描述符  
EACCES 地址被保護,用戶的權限不足  
EADDRNOTAVAIL 接口不存在或者綁定地址不是本地 UNIX協議族,AF_UNIX
EFAULT my_addr指針超出用戶空間 UNIX協議族,AF_UNIX
EINVAL 地址長度錯誤,或者socket不是AF_UNIX族 UNIX協議族,AF_UNIX
ELOOP 解析my_addr時符號鏈接過多 UNIX協議族,AF_UNIX
ENAMETOOLONG my_addr過長 UNIX協議族,AF_UNIX
ENOENT 文件不存在 UNIX協議族,AF_UNIX
ENOMEN 內存內核不足 UNIX協議族,AF_UNIX
ENOTDIR 不是目錄 UNIX協議族,AF_UNIX
EROFS socket節點應該在制度文件系統上 UNIX協議族,AF_UNIX

(4)  if( listen(socket_fd, 10) == -1)

listen:監聽來自客戶端的tcp socket的連接請求;

listen函數在一般在調用bind之後-調用accept之前調用,它的函數原型是:

int listen(int sockfd, int backlog);

返回值:成功返回0,失敗返回-1;

錯誤信息:

EADDRINUSE:另一個socket也在監聽同一個端口。

EBADF:參數sockfd爲非法的文件描述符。ENOTSOCK:參數sockfd不是文件描述符。EOPNOTSUPP:套接字類型不支持listen操作。

參數重點說下backlog:

參數backlog是偵聽隊列的長度。在進程正在處理一個連接請求的時候,可能還存在其它的連接請求。因爲TCP連接是一個過程,所以可能存在一種半連接的狀態,有時由於同時嘗試連接的用戶過多,使得服務器進程無法快速地完成連接請求。如果這個情況出現了,服務器進程希望內核如何處理呢?內核會在自己的進程空間裏維護一個隊列以跟蹤這些完成的連接但服務器進程還沒有接手處理的連接(還沒有調用accept函數的連接),這樣的一個隊列內核不可能讓其任意大,所以必須有一個大小的上限。這個backlog告訴內核使用這個數值作爲上限。

(5) if( (connect_fd = accept(socket_fd, (struct sockaddr*)&client, &LEN)) == -1)

accept函數

int accept(int sockfd, struct sockaddr* addr, socklen_t* len);

返回值是一個新的套接字描述符,他代表的是和客戶端的新的連接,可以把它理解成是一個客戶端的socket,這個socket包含的是客戶端的ip和port。(當然這個new_socket)會從socket中繼承服務器的IP和port信息。參數說明:

(1)、int sockfd:利用系統調用socket()建立的套接字描述符,通過bind()綁定到一個本地地址(一般爲服務器的套接字),並且通過listen()一直在監聽連接

(2)、struct sockaddr* addr: 指向struct sockaddr的指針,該結構用通訊層服務器對等套接字的地址(一般爲客戶端地址)填寫,返回地址addr的確切格式由套接字的地址類別(比如TCP或UDP)決定;若addr爲NULL,沒有有效地址填寫,這種情況下,addrlen也不使用,應該置爲NULL;

(3)、socklen_t* len: 一個值結果參數,調用函數必須初始化爲包含addr所指向結構大小的數值,函數返回時包含對等地址(一般爲服務器地址)的實際數值;

備註:addrlen是個局部整形變量,設置爲sizeof(struct   sockaddr_in)。

返回值

成功時,返回非負整數,該整數是接收到套接字的描述符;出錯時,返回-1,相應地設定全局變量errno。

(6)、之後就是read()函數,write()函數,

n=read(int connect_fd, void*buff,size_t count);

返回值:成功返回讀取的字節數,出錯返回-1並設置errno,
參數就不介紹了
m = write(int connect_fd, const void *buf, size_t count);  
返回值:成功返回寫入的字節數,出錯返回-1並設置errno寫常規文件時,write的返回值通常等於請求寫的字節數
(7)、 close(connect_fd);
      close(socket_fd);

下面是我寫的一個服務器程序,可以打印客戶端的IP和端口號;

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>


#define DEFAULT_PORT 8000  
#define BUFF_SIZE 4096  




int main(int argc, char** argv)
{
    int    socket_fd;
    int    connect_fd;
    struct sockaddr_in     servaddr;
    struct sockaddr_in     client;
    char   buff[BUFF_SIZE];
    int    n;
    int    LEN;
    char addr_p[INET_ADDRSTRLEN];

        //初始化Socket  
        if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 )
        {
                printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
                exit(0);
        }
        else
        {       
                printf("socket ok\n");
        }

        //初始化  
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(DEFAULT_PORT);
 //將本地地址綁定到所創建的套接字上 
        if( bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)
        {
                printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
                exit(0);
        }
        else
        {
                printf("bind ok\n");
        }
        //開始監聽是否有客戶端連接  

        if( listen(socket_fd, 10) == -1)
        {
                printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
                exit(0);
        }

        printf("======waiting for client's request======\n");


        while(1)
        {
        //阻塞直到有客戶端連接,不然多浪費CPU資源。  
                if( (connect_fd = accept(socket_fd, (struct sockaddr*)&client, &LEN ) == -1))
                {
                        printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
                        continue;
                }
                else
                {
                        printf("connect OK\n");
                        bzero(&client, 0);
                        LEN=sizeof(struct sockaddr);
			inet_ntop(AF_INET, &client.sin_addr, addr_p, sizeof(addr_p));
                        printf("client IP is %s,port is %d\n",addr_p, ntohs(client.sin_port));
                }
        //接受客戶端傳過來的數據  


        n = read(connect_fd, buff, BUFF_SIZE);
        buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
        close(connect_fd);
        }
        close(socket_fd);
}
                           

運行的結果:

[lalala@jjjjj ~]$ gcc service.c
[lalala@jjjjj ~]$ ./a.out 
socket ok
bind ok
======waiting for client's request======
connect OK
client IP is 192.168.64.128,port is 31843






























發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章