Linux網絡編程

一、 套接字:源IP地址和目的IP地址以及源端口號和目的端口號的組合稱爲套接字。其用於標識客戶端請求的服務器和服務。編程

經常使用的TCP/IP協議的3種套接字類型以下所示。
(1)流套接字(SOCK_STREAM):數組

流套接字用於提供面向鏈接、可靠的數據傳輸服務。該服務將保證數據可以實現無差錯、無重複發送,並按順序接收。流套接字之因此可以實現可靠的數據服務,緣由在於其使用了傳輸控制協議,即TCP(The Transmission ControlProtocol)協議。

(2)數據報套接字(SOCK_DGRAM):緩存

數據報套接字提供了一種無鏈接的服務。該服務並不能保證數據傳輸的可靠性,數據有可能在傳輸過程當中丟失或出現數據重複,且沒法保證順序地接收到數據。數據報套接字使用UDP(User Datagram Protocol)協議進行數據的傳輸。因爲數據報套接字不能保證數據傳輸的可靠性,對於有可能出現的數據丟失狀況,須要在程序中作相應的處理。

(3) 原始套接字(SOCK_RAW):(通常不用這個套接字)服務器

原始套接字(SOCKET_RAW)容許對較低層次的協議直接訪問,好比IP、 ICMP協議,它經常使用於檢驗新的協議實現,或者訪問現有服務中配置的新設備,由於RAW SOCKET能夠自如地控制Windows下的多種協議,可以對網絡底層的傳輸機制進行控制,因此能夠應用原始套接字來操縱網絡層和傳輸層應用。好比,咱們能夠經過RAW SOCKET來接收發向本機的ICMP、IGMP協議包,或者接收TCP/IP棧不可以處理的IP包,也能夠用來發送一些自定包頭或自定協議的IP包。網絡監聽技術很大程度上依賴於SOCKET_RAW

二、 套接字基本函數:網絡

(1) 建立套接字:
int socket(int family, int type, intprotocol);
功能介紹:
在Linux操做系統中,一切皆文件,這個你們都知道,我的理解建立socket的過程其實就是一個得到文件描述符的過程,固然這個過程會是比較複雜的。能夠從內核中找到建立socket的代碼,而且socket的建立和其餘的listen,bind等操做分離開來。socket函數完成正確的操做是返回值大於0的文件描述符,當返回小於0的值時,操做錯誤。一樣是返回一個文件描述符,可是會由於三個參數組合不一樣,對於數據具體的工做流程不一樣,對於應用層編程來講,這些也是不可見的。
參數說明:
從socket建立的函數能夠看出,socket有三個參數,family表明一個協議族,比較熟知的就是AF_INET,PF_PACKET等;第二個參數是協議類型,常見類型是SOCK_STREAM,SOCK_DGRAM, SOCK_RAW, SOCK_PACKET等;第三個參數是具體的協議,對於標準套接字來講,其值是0,對於原始套接字來講就是具體的協議值。
(2) 套接字綁定函數:
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
功能介紹:
bind函數主要應用於服務器模式一端,其主要的功能是將addrlen長度 structsockaddr類型的myaddr地址與sockfd文件描述符綁定到一塊兒,在sockaddr中主要包含服務器端的協議族類型,網絡地址和端口號等。在客戶端模式中不須要使用bind函數。當bind函數返回0時,爲正確綁定,返回-1,則爲綁定失敗。
參數說明:
bind函數的第一個參數sockfd是在建立socket套接字時返回的文件描述符。
bind函數的第二個參數是structsockaddr類型的數據結構,因爲structsockaddr數據結構類型不方便設置,因此一般會經過對tructsockaddr_in進行地質結構設置,而後進行強制類型轉換成structsockaddr類型的數據,
(3) 監聽函數:
int listen(int sockfd, int backlog);

功能介紹:數據結構

剛開始理解listen函數會有一個誤區,就是認爲其操做是在等在一個新的connect的到來,其實不是這樣的,真正等待connect的是accept操做,listen的操做就是當有較多的client發起connect時,server端不能及時的處理已經創建的鏈接,這時就會將connect鏈接放在等待隊列中緩存起來。這個等待隊列的長度有listen中的backlog參數來設定。listen和accept函數是服務器模式特有的函數,客戶端不須要這個函數。當listen運行成功時,返回0;運行失敗時,返回值位-1.多線程

參數說明:併發

sockfd是前面socket建立的文件描述符;backlog是指server端能夠緩存鏈接的最大個數,也就是等待隊列的長度。
(4) 請求接收函數:
int accept(int sockfd, structsockaddr *client_addr, socklen_t *len);
功能介紹:
接受函數accept其實並非真正的接受,而是客戶端向服務器端監聽端口發起的鏈接。對於TCP來講,accept從阻塞狀態返回的時候,已經完成了三次握手的操做。Accept實際上是取了一個已經處於connected狀態的鏈接,而後把對方的協議族,網絡地址以及端口都存在了client_addr中,返回一個用於操做的新的文件描述符,該文件描述符表示客戶端與服務器端的鏈接,經過對該文件描述符操做,能夠向client端發送和接收數據。同時以前socket建立的sockfd,則繼續監聽有沒有新的鏈接到達本地端口。返回大於0的文件描述符則表示accept成功,不然失敗。
參數說明:
sockfd是socket建立的文件描述符;client_addr是本地服務器端的一個structsockaddr類型的變量,用於存放新鏈接的協議族,網絡地址以及端口號等;第三個參數len是第二個參數所指內容的長度,對於TCP來講其值能夠用sizeof(structsockaddr_in)來計算大小,說要說明的是accept的第三個參數要是指針的形式,由於這個值是要傳給協議棧使用的。
(5)客戶端請求鏈接函數:
int connect(int sock_fd, struct sockaddr *serv_addr,int addrlen);
功能介紹:socket

鏈接函數connect是屬於client端的操做函數,其目的是向服務器端發送鏈接請求,這也是從客戶端發起TCP三次握手請求的開始,服務器端的協議族,網絡地址以及端口都會填充到connect函數的serv_addr地址當中。當connect返回0時說明已經connect成功,返回值是-1時,表示connect失敗。
參數說明:函數

connect的第一個參數是socket建立的文件描述符;第二個參數是一個structsockaddr類型的指針,這個參數中設置的是要鏈接的目標服務器的協議族,網絡地址以及端口號;第三個參數表示第二個參數內容的大小,與accept不一樣,這個值不是一個指針。
在服務器端和客戶端創建鏈接以後是進行數據間的發送和接收,主要使用的接收函數是recv和read,發送函數是send和write。由於對於socket套接字來講,最終實際操做的是文件描述符,因此可使用對文件進行操做的接收和發送函數對socket套接字進行操做。read和write函數是文件編程裏的知識,因此這裏再也不作多與的贅述。

三、 有了以上的知識,那麼咱們就能夠編寫一個簡單的服務器和客戶端了

(1) 簡易服務器:這個服務器只能與一個客戶端相鏈接,若是有多個客戶端就不能用這個服務器進行鏈接。

代碼:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

 
#define PORT 9990   //端口號
#define SIZE 1024   //定義的數組大小
 
int Creat_socket()    //建立套接字和初始化以及監聽函數
{
    int listen_socket = socket(AF_INET, SOCK_STREAM, 0);   //建立一個負責監聽的套接字
    if(listen_socket == -1)
    {
        perror("socket");
        return -1;
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    
    addr.sin_family = AF_INET;  /* Internet地址族 */
    addr.sin_port = htons(PORT);  /* 端口號 */
    addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* IP地址 */
    
    int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr));  //鏈接
    if(ret == -1)
    {
        perror("bind");
        return -1;
    }
    
    ret = listen(listen_socket, 5);        //監聽
    if(ret == -1)
    {
        perror("listen");
        return -1;
    }
    return listen_socket;
}
 
int wait_client(int listen_socket)
{
    struct sockaddr_in cliaddr;
    int addrlen = sizeof(cliaddr);
    printf("等待客戶端鏈接。。。。\n");
    int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen);   //建立一個和客戶端交流的套接字
    if(client_socket == -1)
    {
        perror("accept");
        return -1;
    }
    
    printf("成功接收到一個客戶端:%s\n", inet_ntoa(cliaddr.sin_addr));
    
    return client_socket;
}
 
void hanld_client(int listen_socket, int client_socket)   //信息處理函數,功能是將客戶端傳過來的小寫字母轉化爲大寫字母
{
    char buf[SIZE];
    while(1)
    {
        int ret = read(client_socket, buf, SIZE-1);
        if(ret == -1)
        {
            perror("read");
            break;
        }
        if(ret == 0)
        {
            break;
        }
        buf[ret] = '\0';
        int i;
        for(i = 0; i < ret; i++)
        {
            buf[i] = buf[i] + 'A' - 'a';
        }
        
        printf("%s\n", buf);
        write(client_socket, buf, ret);
        
        if(strncmp(buf, "end", 3) == 0)
        {
            break;
        }
    }
    close(client_socket);
}
 
int main()
{
    int listen_socket = Creat_socket();
    
    int client_socket = wait_client(listen_socket);
    
    hanld_client(listen_socket, client_socket);
    
    close(listen_socket);
    
    return 0;
}
(2) 多進程併發服務器:該服務器就徹底彌補了上一個服務器的不足,能夠同時處理多個客戶端,只要有客戶端來鏈接它,他就能響應。在咱們這個服務器中,父進程主要負責監聽,因此在父進程一開始就要把父進程的接收函數關閉掉,防止父進程在接收函數處阻塞,致使子進程不能建立成功。同理,子進程主要負責接收客戶端,並作相關處理,因此子進程在一建立就要把監聽函數關閉,否則會致使服務器功能的紊亂。這個服務器有一個特別要注意的是,子進程在退出時會產生殭屍進程,因此咱們必定要對子進程退出後進行處理。

代碼:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
 
#define PORT 9990
#define SIZE 1024
 
int Creat_socket()         //建立套接字和初始化以及監聽函數
{
    int listen_socket = socket(AF_INET, SOCK_STREAM, 0);      //建立一個負責監聽的套接字  
    if(listen_socket == -1)
    {
        perror("socket");
        return -1;
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    
    addr.sin_family = AF_INET;  /* Internet地址族 */
    addr.sin_port = htons(PORT);  /* 端口號 */
    addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* IP地址 */
    
    int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr));    //鏈接
    if(ret == -1)
    {
        perror("bind");
        return -1;
    }
    
    ret = listen(listen_socket, 5);   //監聽
    if(ret == -1)
    {
        perror("listen");
        return -1;
    }
    return listen_socket;
}
 
int wait_client(int listen_socket)
{
    struct sockaddr_in cliaddr;
    int addrlen = sizeof(cliaddr);
    printf("等待客戶端鏈接。。。。\n");
    int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen);     //建立一個和客戶端交流的套接字
    if(client_socket == -1)
    {
        perror("accept");
        return -1;
    }
    
    printf("成功接收到一個客戶端:%s\n", inet_ntoa(cliaddr.sin_addr));
    
    return client_socket;
}
 
void hanld_client(int listen_socket, int client_socket)    //信息處理函數,功能是將客戶端傳過來的小寫字母轉化爲大寫字母
{
    char buf[SIZE];
    while(1)
    {
        int ret = read(client_socket, buf, SIZE-1);
        if(ret == -1)
        {
            perror("read");
            break;
        }
        if(ret == 0)
        {
            break;
        }
        buf[ret] = '\0';
        int i;
        for(i = 0; i < ret; i++)
        {
            buf[i] = buf[i] + 'A' - 'a';
        }
        
        printf("%s\n", buf);
        write(client_socket, buf, ret);
        
        if(strncmp(buf, "end", 3) == 0)
        {
            break;
        }
    }
    close(client_socket);
}
 
void handler(int sig)
{
    
    while (waitpid(-1,  NULL,   WNOHANG) > 0)
    {
        printf ("成功處理一個子進程的退出\n");
    }
}
 
int main()
{
    int listen_socket = Creat_socket();
    
 
    signal(SIGCHLD,  handler);    //處理子進程,防止殭屍進程的產生
    while(1)
    {
        int client_socket = wait_client(listen_socket);   //多進程服務器,能夠建立子進程來處理,父進程負責監聽。
        int pid = fork();
        if(pid == -1)
        {
            perror("fork");
            break;
        }
        if(pid > 0)
        {
            close(client_socket);
            continue;
        }
        if(pid == 0)
        {
            close(listen_socket);
            hanld_client(listen_socket, client_socket);
            break;
        }
    }
    
    close(listen_socket);
    
    return 0;
}
(3) 多線程併發服務器:上一個多進程服務器有一個缺點,就是每當一個子進程獲得響應的時候,都要複製父進程的一切信息,這樣就致使了CPU資源的浪費,當客戶端有不少來鏈接這個服務器的時候,就會產生不少的子進程,會致使服務器的響應變得很慢。因此咱們就想到了多線程併發服務器,咱們知道線程的速度是進程的30倍左右,因此咱們就用線程來作服務器。

代碼:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
 
 
#define PORT 9990
#define SIZE 1024
 
int Creat_socket()         //建立套接字和初始化以及監聽函數
{
    int listen_socket = socket(AF_INET, SOCK_STREAM, 0);      //建立一個負責監聽的套接字  
    if(listen_socket == -1)
    {
        perror("socket");
        return -1;
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    
    addr.sin_family = AF_INET;  /* Internet地址族 */
    addr.sin_port = htons(PORT);  /* 端口號 */
    addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* IP地址 */
    
    int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr));    //鏈接
    if(ret == -1)
    {
        perror("bind");
        return -1;
    }
    
    ret = listen(listen_socket, 5);   //監聽
    if(ret == -1)
    {
        perror("listen");
        return -1;
    }
    return listen_socket;
}
 
int wait_client(int listen_socket)
{
    struct sockaddr_in cliaddr;
    int addrlen = sizeof(cliaddr);
    printf("等待客戶端鏈接。。。。\n");
    int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen);     //建立一個和客戶端交流的套接字
    if(client_socket == -1)
    {
        perror("accept");
        return -1;
    }
    
    printf("成功接收到一個客戶端:%s\n", inet_ntoa(cliaddr.sin_addr));
    
    return client_socket;
}
 
void hanld_client(int listen_socket, int client_socket)    //信息處理函數,功能是將客戶端傳過來的小寫字母轉化爲大寫字母
{
    char buf[SIZE];
    while(1)
    {
        int ret = read(client_socket, buf, SIZE-1);
        if(ret == -1)
        {
            perror("read");
            break;
        }
        if(ret == 0)
        {
            break;
        }
        buf[ret] = '\0';
        int i;
        for(i = 0; i < ret; i++)
        {
            buf[i] = buf[i] + 'A' - 'a';
        }
        
        printf("%s\n", buf);
        write(client_socket, buf, ret);
        
        if(strncmp(buf, "end", 3) == 0)
        {
            break;
        }
    }
    close(client_socket);
}
 
int main()
{
    int listen_socket = Creat_socket();
    
    while(1)
    {
        int client_socket = wait_client(listen_socket);
        
        pthread_t id;
        pthread_create(&id, NULL, hanld_client, (void *)client_socket);  //建立一個線程,來處理客戶端。
        
         pthread_detach(id);   //把線程分離出去。
    }
    
    close(listen_socket);
    
    return 0;
}
 
(4)客戶端:客戶端相對於服務器來講就簡單多了,客戶端只須要建立和服務器相鏈接的套接字,而後對其初始化,而後再進行鏈接就能夠了,鏈接上服務器就能夠發送你想發送的數據了。
代碼:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
 
 
#define PORT 9990
#define SIZE 1024
 
int main()
{
    int client_socket = socket(AF_INET, SOCK_STREAM, 0);   //建立和服務器鏈接套接字
    if(client_socket == -1)
    {
        perror("socket");
        return -1;
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    
    addr.sin_family = AF_INET;  /* Internet地址族 */
    addr.sin_port = htons(PORT);  /* 端口號 */
    addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* IP地址 */
    inet_aton("127.0.0.1", &(addr.sin_addr));
 
    int addrlen = sizeof(addr);
    int listen_socket =  connect(client_socket,  (struct sockaddr *)&addr, addrlen);  //鏈接服務器
    if(listen_socket == -1)
    {
        perror("connect");
        return -1;
    }
    
    printf("成功鏈接到一個服務器\n");
    
    char buf[SIZE] = {0};
    
    while(1)        //向服務器發送數據,並接收服務器轉換後的大寫字母
    {
        printf("請輸入你相輸入的:");
        scanf("%s", buf);
        write(client_socket, buf, strlen(buf));
        
        int ret = read(client_socket, buf, strlen(buf));
        
        printf("buf = %s", buf);
        printf("\n");
        if(strncmp(buf, "END", 3) == 0)     //當輸入END時客戶端退出
        {
            break;
        }
    }
    close(listen_socket);
    
    return 0;
}
相關文章
相關標籤/搜索