網絡套接字的應用

在介紹套接字前,我們需要了解什麼是IP地址,什麼是端口號port

1.IP地址
ip協議有兩個版本,IPv4 和 IPv6 (一般情況下使用IPv4);
IPv4協議佔32個bit;IPv6佔128個bit是IPv4的4倍;

1) IP地址是在IP協議中用來標識網絡中不同主機的地址;
2) 對於IPv4來說,IP地址是一個4字節,32位的整數
3) 我們通常也是用“ 點分十進制 ”的字符串表示IP地址,例如 192.168.0.1 ;用點分割的每一個數字表示一個字節,範圍是 0 ~ 255;

2.端口號
端口號是傳輸層協議的內容;
1)端口號是一個2字節16位的整數
2)端口號用來標識一個進程,告訴操作系統,當前的這個數據要交給哪一個進程來處理;
3)IP地址+端口號 能夠標識網絡上的某一臺主機的某一個進程
4)一個端口號只能被一個進程佔用
(注:一個進程可以綁多個端口號,但一個端口號只能被一個進程綁定;)

一、套接字簡單介紹
1.什麼是套接字
在Unix/Linux中,一切皆文件。那對於這兩個操作系統而言,“端點”就是一個特殊的文件,也就是說Socket實際上就是文件。既然Socket是文件,那就可以用“打開open –> 讀寫write/read –> 關閉close”模式來操作它,一些socket函數就是對其進行的操作(讀/寫IO、打開、關閉)

2.對於一個Socket而言,它至少需要3個參數來指定:
  1)通信的目的地址;
  2)使用的傳輸層協議(如TCP、UDP);
  3)使用的端口號。

3.套接字類型是指創建套接字的應用程序要使用的通信服務類型。linux系統支持多種套接字類型,最常用的有以下三種:
  1)SOCK_STREAM:流式套接字,提供面向連接、可靠的數據傳輸服務,數據按字節流、按順序收發,保證在傳輸過程中無丟失、無冗餘。TCP協議支持該套接字。
  2)SOCK_DGRAM:數據報套接字,提供面向無連接的服務,數據收發無序,不能保證數據的準確到達。UDP協議支持該套接字。
  3)SOCK_RAW:原始套接字。允許對低於傳輸層的協議或物理網絡直接訪問,例如可以接收和發送ICMP報文。常用於檢測新的協議。

4.套接字(socket)編程接口

    //創建socket文件描述符(TCP/UDP,客戶端 + 服務器)
    int socket(int domain,int type,int protocol);

    //綁定端口號(TCP/UDP,服務器)
    int bind(int socket,const struct sockaddr* address,socklen_t address_len);

    //監聽(TCP,服務器)
    int listen(int socket,int bakclog);

    //接受請求,生成新的套接字(TCP,服務器)
    int accept(int socket,struct sockaddr* address,socklen_t addrlen);

    //建立連接(TCP ,服務器)
    int connect(int socket,const struct sockaddr* addr,socklen_t addrlen);

這裏寫圖片描述

//sockaddr結構
    struct sockaddr
    {
        _SOCKADDR_COMMON( sa_ );
        char sa_data[14];
    }

//sockaddr_in結構
struct sockaddr_in
{
    _SOCKADDR_COMMON(sin_);
    in_port_t sin_port;
    struct in_addr sin_addr;

    unsigned char sin_zero[sizeof(struct sockaddr)-
                                 _SOCKADDR_COMMON_SIZE-
                                 sizeof(in_port)-
                                 sizeof(struct in_addr)];
};

//在基於IPv4編程時,使用的數據結構是sockaddr_in;這個結構體裏主要有三部分信息:地址類型、端口號、IP地址

//in_addr結構
typedef uint32_t in_addr_t;
struct in_addr
{
        in_addr_t s_addr;
};
//in_addr用來表示一個Ipv4的Ip地址。就是一個32位的整數。

5.網絡協議中的字節序
所有的網絡協議爲了統一,全都使用的是大端存儲——>下面介紹幾個字節序轉換函數

#include<arpa/inet.h>

uint32_t htonl(uint32_t hostlong);    //主機序列轉換成網絡序列
uint16_t htons(uint16_t hostshort);    //主機序列轉換成網絡序列
uint32_t ntohl(uint32_t netlong);     //網絡序列轉換成主機序列
uint16_t ntohs(uint32_t netshort);    //網絡序列轉換成主機序列

二、基於UDP協議編寫簡單的服務器

1.服務器中的主要操作
(1)創建套接字 int sock=socket( AF_INET , SOCK_DGRAM , 0); 參數SOCK_DGRAM表示UDP。
(2)綁定,bind之後就可以直接進行通信了。
(3)使用sendto和recvfrom來進行數據讀寫。

2.客戶端的主要操作
(1)創建套接字。
(2)使用sendto和recvfrom來進行數據讀寫。

3.幾個重要的函數

//字符串轉in_addr函數
#include<arpa/inet.h>

int inet_aton(const char*strptr,struct in_addr* addrptr);
in_addr_t inet_addr(const char* strptr);
int inet_pton(int family,const void* addrptr,char* strptr,size_t len);

//in_addr轉字符串函數
char* inet_ntoa(struct in_addr inaddr);
const char* inet_ntop(int family,const char* addrptr,void * strptr,size_t len);

//其中inet_pton和inet_ntop不僅可以轉換IPv4的addr,也可以轉換Ipv6的in6_addr,因此函數接口是void* addrptr 。

4.代碼實現
【server.c】

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

int main(int argc,char* argv[])
{
    int sock=socket(AF_INET,SOCK_DGRAM,0);   //創建套接字
    if(sock<0)
    {
        perror("socket");
        return 2;
    }

    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(atoi(argv[2]));
    local.sin_addr.s_addr=inet_addr(argv[1]);

    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)  //綁定
    {
        perror("bind");
        return 3;
    }

    char buf[1024];
    struct sockaddr_in client;
    while(1)
    {
        socklen_t len=sizeof(client);
        size_t s=recvfrom(sock,buf,sizeof(buf)-1,0,\
                (struct sockaddr*)&client,&len);              //接收信息
        if(s>0)
        {
            buf[s]=0;
            printf("[%s:%d]:%s\n",inet_ntoa(client.sin_addr),\
                    ntohs(client.sin_port),buf);
            sendto(sock,buf,strlen(buf),0,\
                    (struct sockaddr*)&client,sizeof(client));    //發送信息

        }
    }
    return 0;
}

【client.c】

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


int main(int argc,char* argv[])
{
    int sock=socket(AF_INET,SOCK_DGRAM,0);      //創建套接字
    if(sock<0)
    {
        perror("socket");
        return 2;
    }

    struct sockaddr_in server;
    server.sin_family=AF_INET;
    server.sin_port=htons(atoi(argv[2]));
    server.sin_addr.s_addr=inet_addr(argv[1]);

    char buf[1024];
    struct sockaddr_in peer;
    while(1)
    {
        socklen_t len=sizeof(peer);
        printf("Please Enter: ");
        fflush(stdout);
        size_t s=read(0,buf,sizeof(buf)-1);
        if(s>0)
        {
            buf[s-1]=0;
            sendto(sock,buf,strlen(buf),0,\
                    (struct sockaddr*)&server,sizeof(server));  //發送信息
            size_t _s=recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);  //接收信息
            if(_s>0)
            {
                buf[_s]=0;
                printf("server echo# %s\n",buf);
            }
        }
    }

    return 0;
}

三、基於TCP協議編寫簡單的服務器
1.服務器的主要操作
(1)創建套接字 int sock=socket(AF_INET , SOCK_STREAM , 0); 參數SOCK_STREAM表示TCP
(2)綁定 bind
(3)監聽套接字狀態 listen
(4)accept監聽套接字,獲取新的文件描述符
(5)讀取信息,發送信息等功能的實現

2.客戶端的主要操作
(1)創建套接字 int sock=socket(AF_INET , SOCK_STREAM , 0); 參數SOCK_STREAM表示TCP
(2)連接到服務器 connect
(3)發送信息、接收信息功能的實現

3.幾個重要的函數

    //(1)bind綁定函數
        #include<sys/types.h>
        #include<sys/socket.h>

        int bind(int socket,const struct sockaddr* addr,socklen_t addrlen);
        //成功返回0,失敗返回-1

    //(2)listen監聽函數
    #include<sys/types.h>
    #include<sys/socket.h>

    int listen(int socket,int backlog);
    //成功返回0,失敗返回-1
    //backlog 表示的是等待隊列的個數,由服務器維護,必須要有但不要太長

    //(3)accept接收連接函數
    #include<sys/types.h>
    #include<sys/socket.h>

    int accept(int socket,struct sockaddr* addr ,socklen_t* addrlen);
    //addr是一個傳出參數
    //返回值是新的進行操作的套接字

    //(4)對myaddr參數的初始化
    bzero(&servaddr,sizeof(servaddr));   //將整個結構清0
    servaddr.sin_family=AF_INET;           //設置地址類型位AF_INET
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);  //INADDR_ANY該宏表示本地的任意IP
    servaddr.sin_port=htons(SERV_PORT);      //SERV_PORT 表示端口號9999 ,

    //(5)connect連接函數
    #include<sys/types.h>
    #include<sys/socket.h>

    int connect(int socket,const struct sockaddr* addr,socklen_t addrlen);
    //客戶端用來連接服務器
    //connect與bind的參數形式一致,區別在於bind的參數是自己的地址,而connect的參數是對方的地址
    //成功返回0,失敗返回-1;

4.代碼實現
【server.c】

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

#define _PORT 9999    //端口號
#define _BACKLOG 10     //等待隊列個數

int main()
{
    int sock=socket(AF_INET,SOCK_STREAM,0);   //創建套接字
    if(sock<0)
    {
        perror("socket!");
        return 2;
    }
    struct sockaddr_in client_sock; 

    struct sockaddr_in server_sock;
    bzero(&server_sock,sizeof(server_sock));
    server_sock.sin_family=AF_INET;
    server_sock.sin_addr.s_addr=htonl(INADDR_ANY);
    server_sock.sin_port=htons(_PORT);

    if(bind(sock,(struct sockaddr*)&server_sock,sizeof(struct sockaddr_in))<0)  //綁定
    {
        perror("bind!");
        close(sock);
        return 3;
    }

    if(listen(sock,_BACKLOG)<0)       //監聽
    {
        perror("listen!");
        close(sock);
        return 4;   
    }

    printf("bind and listen success,wait accept...\n");
///////////////////////////////////////////
    while(1)
    {
        socklen_t len=0;
        int newsock=accept(sock,(struct sockaddr*)&client_sock,&len);   //接收連接請求
        if(newsock<0)
        {
            perror("accept!");
            close(sock);
            return 5;
        }
        char buf_ip[INET_ADDRSTRLEN];
        memset(buf_ip,0,sizeof(buf_ip));
        inet_ntop(AF_INET,&client_sock.sin_addr,buf_ip,sizeof(buf_ip));

        printf("get connect,ip is:%s port is:%d\n",buf_ip,ntohs(client_sock.sin_port));

        while(1)
        {
            char buf[1024];

            int s=read(newsock,buf,sizeof(buf)-1);     //接收信息
            if(s>1)
            {
                buf[s]=0;
                printf("client:# %s\n",buf);
                write(newsock,buf,strlen(buf));      //發送信息
            }
            else if(s==0)
            {
                printf("client [ip:%s | %d] exit!\n",buf_ip,ntohs(client_sock.sin_port));
                break;
            }

        }
    }
    close(sock);             //關閉套接字(文件描述符)
    return 0;
}

【client.c】

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

#define SERVER_PORT 9999
//

int main(int argc,char*argv[])
{
    if(argc!=2)
    {
        perror("usage");
        return 1;
    }
    char*str=argv[1];
    char buf[1024];
    memset(buf,0,sizeof(buf));

    int sock=socket(AF_INET,SOCK_STREAM,0);  //創建套接字
    if(sock<0)
    {
        perror("socket!");
        return 2;
    }

    struct sockaddr_in server_sock;             //
    bzero(&server_sock,sizeof(server_sock));
    server_sock.sin_family=AF_INET;
    inet_pton(AF_INET,str,&server_sock.sin_addr);
    server_sock.sin_port=htons(SERVER_PORT);

    int ret=connect(sock,(struct sockaddr*)&server_sock,sizeof(server_sock));  //請求連接

    printf("connect success...\n");
///////////////////////////////////////////
    while(1)
    {
        printf("client :#");
        fgets(buf,sizeof(buf),stdin);
        buf[strlen(buf)-1]=0;
        write(sock,buf,sizeof(buf));

        if(strncasecmp(buf,"quit",4)==0)
        {
            printf("quit!\n");
            break;
        }
        read(sock,buf,sizeof(buf));
        printf("server :$ %s\n",buf);
    }
    close(sock);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章