歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Linux IPC tcp/ip socket 編程

Linux IPC tcp/ip socket 編程

日期:2017/3/1 9:10:12   编辑:Linux編程

模型

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//服務器:  

socket()              //創建socket                    
struct sockaddr_in    //准備通信地址      
bind()                //綁定socket和addr
listen()              //創建listening socket
accept()              //創建connect socket
send()/recv()         //進行通信            
close()               //關閉socket            

//客戶端:

socket()              //創建socket
准備通信地址:服務器的地址
connect()             //鏈接socket和通信地址
send()/recv()         //進行通信
close()               //關閉socket

socket()

//創建網絡端點,返回socket文件描述符,失敗返回-1設errno
int socket(int domain, int type, int protocol);

domain :協議族(protocol family)(網絡通訊(IP)還是本地通訊(xxx.socket))

  • AF_INET用於實現給予ipv4網絡協議的網絡協議

type :協議(TCP還是UDP)

  • SOCK_STREAM //流式套接字, 實現包括但不限於TCP協議,which is有序,可靠雙向面向連接的字節流通信方式

protocol: 特殊協議, 一般給0

准備通信地址:

struct sockaddr{    //主要用於函數的形參類型, 很少定義結構體變量使用, 叫做通用的通信地址類型//$man bind
    sa_family_t     sa_family;
    char            sa_data[14];
}
struct sockaddr_in{ //准備網絡通信的通信地址   //$man in.h
    sa_family_t sin_family;     //協議族, 就是socket()的domain的AF_INET
    in_port_t       sin_port;   //端口號
    struct in_addr  sin_addr;   //IP地址
}
struct in_addr{ 
    in_addr_t   s_addr;     //整數類型的IP地址
}

bind():

//把通信地址和socket文件描述符綁定,用在服務器端,成功返回0,失敗返回-1設errno
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd: socket文件的fd(returned by socket())
addr: 需要強制類型轉換成socketaddr_un或soketaddr_in, 參見上
addrlen: 通信地址的大小, 使用sizeof();

listen()

//創建偵聽socket,把sockfd標記的socket標記為被動socket,被動socket表示這個socket只能用來接收即將到來的連接請求,不再用於讀寫操作和通信,接收連接請求的是accept()
//成功返回0,失敗返回-1設errno
int listen(int sockfd, int backlog);

backlog:排隊等待“被響應”連接請求隊列的最大長度 eg: 接待室的最大長度

accept()

//創建連接socket,返回連接socket的文件描述符,成功返回文件描述符,失敗返回-1設errno
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

addr : 結構體指針, 用於帶出客戶端的通信地址
addlen : 結構體指針, 用於帶出通信地址的大小
ATTENTION: listen()把socket()創建的sockfd變為listening socket, 負責偵聽哪個client連接上了(即不但要知道連上沒, 還要知道誰連上了, 這個SOCK_STREAM的socket有這個能力), accept()提取排隊中的最上面的一個client, 給它一個conneted socket, 這樣這個client就可以和server溝通了, 就是說這裡有兩個socket, 一個負責偵聽一個負責通信

send()

//向指定的socket發送指定的數據,成功返回實際發送數據的大小,失敗返回-1設errno
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

sockfd: 用於通信的socket描述符(returned by accept())
buf : 被發送數據的緩沖區首地址
len : 被發送數據的大小
flags: 發送的標志, 如果給0等同於write()

recv()

//從指定的socket接收數據,成功返回接收的數據的大小,失敗返回-1設errno
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

sockfd: 用於通信的socket描述符(returned by accept())
buf: 接收數據的緩沖區首地址
len: 接收數據的大小
flags: 發送的標志, 如果給0等同於read()

connect():

//初始化一個socket的連接,用在客戶端,成功返回0,失敗返回-1設errno
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd: socket文件的fd(returned by socket())
addr: 需要強制類型轉換成socketaddr_un或soketaddr_in, 參見上
addrlen: 通信地址的大小, 使用sizeof();

例子-多進程並發tcp/ip協議服務器模型

除了這種多進程的並發模型,還有多線程並發和I/O多路復用並發等方式

//創建server, 用多進程同時響應多個client的請求, 當client發來 “bye”的時候斷開連接, 按下Ctrl+C關閉服務器
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>      //省略了幾個頭文件
int sockfd; //全局變量

void fa(int signo){
    printf("closing server...\n");
    sleep(3);
    int res=close(sockfd);
    if(-1==res)
        perror("close"),exit(-1);
    printf("server closed\n");
    exit(0);
}
int main(){
    …   //同上一個程序
    //set SIGINT
    printf("Press ctrl+c to close server\n");
    if(SIG_ERR==signal(SIGINT,fa))      //整個程序,包括第一個while(1)是通過信號處理終止的
        perror("signal"),exit(-1);
 
    while(1){   //只要有client接入就創建新進程與之通信
        struct sockaddr_in recv_addr;
        socklen_t len=sizeof(recv_addr);
        int CnnSockfd=accept(sockfd,(struct sockaddr*)&recv_addr,&len);     
            //如果偵聽隊列裡面有client就accept(), 否則就在這阻塞著,不繼續執行,除非遇到Ctrl+C終止整個進程
        if(-1==CnnSockfd)
            perror("accept"),exit(-1);
        char *ip=inet_ntoa(recv_addr.sin_addr);
        printf("client:%s linked\n",ip);
        
        pid_t pid=fork();
        if(-1==pid)
            perror("fork()"),exit(-1);
        if(0==pid){
            if(SIG_ERR==signal(SIGINT,SIG_DFL))
                perror("signal"),exit(-1);
            //每個child處理一個client,所以已經不需要listening socket了,可以把它關了
            //如果不關子進程也會有一個listenfd,會和父進程一起搶,不應該
            res=close(sockfd);  
            if(-1==res)
                perror("close"),exit(-1);
            while(1){           //只要client發數據就處理,除非遇到 “bye”
                char buf[100]={0};
                res=recv(CnnSockfd,buf,sizeof(buf),0);
                if(-1==res)
                    perror("recv"),exit(-1);
                printf("client%s,data sent:%s\n",ip,buf);
                if(!strcmp(buf,"bye")){     //遇到“bye”就不再待命,break掉准備斷開連接
                    printf("client%s has been unlinked\n",ip);
                    break;
                }
                res=send(CnnSockfd,"I received!",12,0);
                if(-1==res)
                    perror("send"),exit(-1);
            }
            res=close(CnnSockfd);           //斷開連接即close(相應的connected socket)
            if(-1== res)
                perror("close"),exit(-1);
            exit(0);                        //斷開了連接了,就可以exit子進程了
        }
        res=close(CnnSockfd);   //
        if(-1==res)
            perror("close"),exit(-1);
    }
    return 0;
}

Copyright © Linux教程網 All Rights Reserved