Socket編程實踐(2) Socket API 與 簡單例程

在本篇文章中,先介紹一下Socket編程的一些API,而後利用這些API實現一個客戶端-服務器模型的一個簡單通訊例程。該例子中,服務器接收到客戶端的信息後,將信息從新發送給客戶端。編程

socket()函數

socket()函數用於建立一個套接字。這就好像購買了一個電話。不過該電話尚未分配號碼。服務器

#include <sys/types.h>         

 #include <sys/socket.h>



 int socket(int domain, int type, int protocol)

參數說明:網絡

  • domain:指定通訊的協議族,這些協議族定義在頭文件< sys/socket.h >中。使用IPV4協議族時,該參數設置爲AF_INETdom

  • type :指定socket的類型。在上一篇文章中介紹過,套接字經常使用的有三種類型:流式套接字SOCK_STREAM,數據報套接字SOCK_DGRAM,原始套接字SOCK_RAWsocket

  • protocol : 該參數指定了一種協議類型用於所選擇的套接字。若是僅有一種協議支持某種套接字類型,那麼該參數能夠定義爲0,此時使用默認協議;若是一種套接字類型可能有多種協議類型,那麼必須顯式指定協議類型。關於具體細節,能夠man socket進行查閱。函數

socket()的返回值:成功時返回非負整數;失敗時返回-1;ui

bind() 函數

bind()函數綁定一個本地地址到套接字上,這至關於爲電話綁定了號碼。當一個套接字經過socket()被建立,它並無綁定到具體的地址上,bind()來完成這個步驟。 bind()函數的函數原型以下:code

#include <sys/types.h>    

 #include <sys/socket.h>



int bind(int sockfd, const struct sockaddr *addr,

        socklen_t addrlen);

參數說明:接口

  • sockfd:socket()函數建立後成功返回的套接字隊列

  • addr : 須要綁定的地址

  • addrlen:套接字的大小

這裏須要使用到sockaddr_in結構來表示一個地址,該結構以下:

struct sockaddr_in

{

sa_family_t  sin_family;

in_port_t    sin_port;

struct in_addr sin_addr;

};

struct in_addr

{

uint32_t s_addr;

}

sockaddr_in須要強制轉換爲struct sockaddr*類型,傳遞給bind()函數的第二個參數。下面是一段例程:

int main()

{

int listenfd = socket(AF_INET,SOCK_STREAM,0);

if(listenfd == -1)

err_exit("socket error");

struct sockaddr_in addr;

//填充結構

addr.sin_family  = AF_INET;

addr.sin_port= htons(8001); //主機字節序轉換爲網絡字節序

addr.sin_addr= htonl(INADDR_ANY);//綁定主機的任一個IP地址

/*下面兩句具備相同的功能:都是綁定到本機ip地址*/

//inet_aton("127.0.0.1",&addr.sin_addr);

//addr.sin_addr.s_addr = inet_addr("127.0.0.1");



if(bind(listenfd,(const struct sockaddr*)&addr,sizeof(addr))==-1)

err_exit("bind error");

}

listen()函數

當使用socket()建立了一個套接字時,該套接字默認是主動套接字。使用listen()函數會使套接字稱爲一個被動套接字,也就是說,該套接字將被用來接受鏈接的數據,這些數據經過accept()函數接收。

listen()函數的函數原型以下:

#include <sys/types.h>         

 #include <sys/socket.h>

 int listen(int sockfd, int backlog);

參數說明:

  • sockfd : 套接字。

  • backlog: 指定鏈接隊列的長度。

對於給定的監聽套接字,內核須要維護兩個隊列:

  1. 已完成鏈接隊列:該隊列中的鏈接處於ESTABLISHED狀態,也便是已經完成了三次握手過程。

  2. 未完成鏈接隊列:該隊列中的鏈接處於SYN_RCVD狀態,還未創建鏈接。

兩個隊列的長度之和不可以超過backlogi。若是一個鏈接請求到達時未完成隊列已滿,客戶端可能接收到一個錯誤指示ECONNREFUSED。服務器使用accept()函數從已完成鏈接隊列的隊頭返回一個鏈接。下面是TCP爲監聽套接口維護的兩個隊列:

accept()函數

accept()函數用於從已完成隊列的隊頭返回一個鏈接。它的函數原型爲:

#include <sys/types.h>          

#include <sys/socket.h>



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

參數說明:

  • sockfd : 服務器套接字

  • addr :用於接收對等方(客戶端)的套接字地址。該參數填充爲NULL時,不接收任何信息。

  • addrlen:返回對等方的套接字地址長度。若是不關心能夠設置爲NULL,不然必定要初始化。

函數返回值:成功返回一個非負整數,表明一個套接字;失敗返回-1;

connect()函數

該函數用於創建一個鏈接到指定的套接字。函數的原型爲:

#include <sys/types.h>      

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,

                   socklen_t addrlen);

參數說明:

  • sockfd : 未鏈接的套接字

  • addr:未鏈接的套接字地址

  • addrlen:addr的長度

一個簡單的socket 通訊例程

客戶端代碼:

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

#define ERR_EXIT(m)\
        do \
        {\
                perror(m);\
                exit(EXIT_FAILURE);\
        }while(0)

int main()
{
        /*建立一個套接字*/
        int sock = socket(AF_INET,SOCK_STREAM,0);
        if(sock == -1)
              ERR_EXIT("socket");

        /*定義一個地址結構*/
        struct sockaddr_in servaddr;
        memset(&servaddr,0,sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5888);
        servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

        /*進行鏈接*/
        if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
        {
                ERR_EXIT("connect");
        }
        else
        {
                printf("鏈接成功\n");
        }
        char sendbuf[1024]={0};
        char recvbuf[1024]={0};
        /*從標準輸入中讀入*/
        while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
        {
                write(sock ,sendbuf,strlen(sendbuf));
                if(read (sock,recvbuf,sizeof(recvbuf))>0)
                {
                        printf("從服務器接收信息:\n");
                        fputs(recvbuf,stdout);
                }
                memset(&sendbuf,0,sizeof(sendbuf));
                memset(&recvbuf,0,sizeof(recvbuf));
        }
        close(sock);
        return 0;
    }

服務器端代碼:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<string.h>
#define ERR_EXIT(m)\
        do \
        {\
                perror(m);\
                exit(EXIT_FAILURE);\
        }while(0)

int main()
{
        /* 建立一個套接字*/

        int listenfd= socket(AF_INET ,SOCK_STREAM,0);
        if(listenfd==-1)
                ERR_EXIT("socket");

        /*定義一個地址結構並填充*/
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;   //協議族爲ipv4
        addr.sin_port = htons(5888); //綁定端口號
        addr.sin_addr.s_addr = htonl(INADDR_ANY);//主機字節序轉爲網絡字節序

        /*將套接字綁定到地址上*/
        if(bind(listenfd,(const struct sockaddr *)&addr ,sizeof(addr))==-1)
        {
                ERR_EXIT("bind");
        }
        /*監聽套接字,成爲被動套接字*/
        if(listen(listenfd,SOMAXCONN)<0)
        {
                ERR_EXIT("Listen");
        }
        struct sockaddr_in peeraddr;
        socklen_t peerlen = sizeof(peeraddr);

        /*定義一個套接字,一般稱爲已鏈接套接字*/
        int conn ;
        conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);
        if(conn <0)
            ERR_EXIT("accept error");
        else
            printf("鏈接到服務器的客戶端的IP地址是:%s,端口號是:%d\n",inet_ntoa(peeraddr.sin_addr),htons(peeraddr.sin_port));

        /*循環獲取數據、發送數據*/
        char recvbuf[1024];
        while(1)
        {
                memset(recvbuf,0,sizeof(recvbuf));
                int ret = read(conn,recvbuf ,sizeof(recvbuf));
                fputs(recvbuf,stdout);
                write(conn,recvbuf,sizeof(recvbuf));
        }
        /*關閉套接字*/
        close(listenfd);
        close(conn);
        return 0;
}
相關文章
相關標籤/搜索