TCP、UDP有關函數

首先看一下上一篇三次握手四次揮手文章中提到的原理圖。
其中的read對應的就是recv函數,write對應的就是send函數。
TCP原理圖及函數
步入正題,函數的使用:


1.TCP客戶端


socket : 創建套接字

函數原型:int socket(int family,int type,int protocol);
功能:創建一個用於網絡通信的socket套接字(描述符)
參數:
	@family  :  協議族(AF_INET、AF_INET6、PF_PACKET)
	@type    :  套接字類(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW)
	@protocol:  協議類別(0、IPPROTO_TCP、IPPROTO_UDP)(一般傳0,自動匹配)
返回值:套接字
特點:創建套接字時,系統不會分配端口。
	創建的套接字默認屬性是主動的,即主動發起服務的請求;
	當作爲服務器時,往往需要修改爲被動的。(listen)
頭文件:  
	#include <sys/socket.h>

connect : 連接“服務器”

函數原型:
	int connect(int sockfd,const struct sockaddr *addr,socklen_t len);
功能:主動跟服務器建立鏈接
參數:
	@sockfd   :   socket 套接字
	@addr     :   連接的服務器地址結構
	@len      :   地址結構體長度
返回值:
	成功  :  0   失敗  :  其他
注意:
	connect 建立連接之後不會產生新的套接字;
	連接成功後纔可以開始傳輸 TCP 數據。
頭文件:
	#include <sys/socket.h>

send : 發送數據到“服務器”

函數原型:
	ssize_t send(int sockfd, const void* buf,size_t nbytes, int flags);
功能:用於發送數據
參數:
	@sockfd   :    已建立連接的套接字
	@buf      :    發送數據的地址
	@nbytes   :    發送緩數據的大小(以字節爲單位)
	@flags    :    套接字標誌(常爲0)
返回值:成功發送的字節數
注意:
	不能用發送 0 長度的數據包,這是與sendto的差別。
頭文件:
	#include <sys/socket.h>

recv : 接受“服務器”的響應

函數原型:
	ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
功能:用於接收網絡數據
參數:
	@sockfd    :    套接字
	@buf       :    接收網絡數據的緩衝區的地址
	@nbytes    :    接收緩衝區的大小(以字節爲單位)
	@flags     :    套接字標誌(常爲 0)
返回值:成功接收到字節數
注意:
	一旦接收到0長度的數據包,表示通信結束.
頭文件:
	#include <sys/socket.h>

close : 關閉連接

close(socketfd);  //不用瞭解太多

2.TCP服務器


socket : 創建套接字

函數原型:int socket(int family,int type,int protocol);
功能:創建一個用於網絡通信的socket套接字(描述符)
參數:
	@family  :  協議族(AF_INET、AF_INET6、PF_PACKET)
	@type    :  套接字類(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW)
	@protocol:  協議類別(0、IPPROTO_TCP、IPPROTO_UDP)(一般傳0,自動匹配)
返回值:套接字
特點:創建套接字時,系統不會分配端口。
	創建的套接字默認屬性是主動的,即主動發起服務的請求;
	當作爲服務器時,往往需要修改爲被動的。(listen)
頭文件:  
	#include <sys/socket.h>

bind : 確定一個具體的地址

函數原型:
	int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
功能:將本地協議地址與 sockfd 綁定(只能綁定本地主機的IP)
參數:
    @sockfd    :   socket 套接字
    @myaddr    :   指向特定協議的地址結構指針(需要綁定的具體的ip以及port信息)
    @addrlen   :   該地址結構的長度
返回值:
    成功:返回 0          失敗:其他

listen : 由主動變被動、創建鏈接隊列、並讓操作系統知道這是一個服務 器、而不是客戶端。

函數原型:int listen(int sockfd, int backlog);
功能:將套接字由主動修改爲被動;
	使操作系統爲該套接字設置一個連接隊列;
	用來記錄所有連接到該套接字的連接。
參數:
	@sockfd    :   socket 監聽套接字
	@backlog   :   連接隊列的長度
返回值:
	成功:返回0           失敗:其他

accept

函數原型:
	int accept(int sockfd,struct sockaddr *cliaddr, socklen_t *addrlen);
功能:從已連接隊列中取出一個已經建立的連接,
	如果沒有任何連接可用,則進入睡眠等待(阻塞)
參數:
	@sockfd     :   socket 監聽套接字
	@cliaddr    :   用於存放客戶端套接字地址結構
	@addrlen    :   套接字地址結構體長度的地址
返回值:
	已連接套接字。(最好while一直探測,套接字接收返回值,建立線程處理這一連接操作)
注意:
	返回的是一個已連接套接字,這個套接字代表當前這個連接。
頭文件:
	#include <sys/socket.h>

接收、發送、關閉在上面已經表明。
給大家一個echo併發服務器的例子(原理接收到什麼就原路發回去什麼----echo)

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
void *deal_client(void *arg);
int main(int argc, char const *argv[])
{
    //sockfd監聽套接字(不是用來和客戶端通信  只是接受客戶端的鏈接請求)
    int sockfd = socket(AF_INET,SOCK_STREAM,0);

    //服務器必須bind一個固定的ip port8080
    struct sockaddr_in my_addr;
    bzero(&my_addr,sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(atoi(argv[1]));
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(sockfd,(struct sockaddr*)&my_addr,sizeof(my_addr));

    //使用listen 由主動變被動 創建鏈接隊列
    listen(sockfd, 10);

    //使用accept提取已完成鏈接的客戶端
    while(1)
    {
        struct sockaddr_in client_addr;
        socklen_t len = sizeof(client_addr);
        //阻塞
        int new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &len);
        //new_fd 已連接套接字  代表和客戶端的真正鏈接
        //創建一個單獨的線程 服務於客戶端
        pthread_t tid;
        pthread_create(&tid,NULL,deal_client, &new_fd);
        pthread_detach(tid);

    }

    //關閉監聽套接字
    close(sockfd);
    return 0;
}

void *deal_client(void *arg)
{
    int new_fd = *(int *)arg;
     //和客戶端通信一下(echo服務器)客戶端連接服務器 併發送數據給服務器 服務器收到數據 同時轉發給客戶端
    char buf[128]="";
    //收
    int ret = recv(new_fd, buf,sizeof(buf),0);
    //原樣轉發
    send(new_fd,buf,ret,0);

    //關閉已連接套接字
    close(new_fd);
}

**

3.UDP有關函數

**
經過上面的敘述,UDP涉及到的函數只有sendto、recvfrom不知道。
sendto : 發送數據

函數原型:
	ssize_t sendto(int sockfd,const void *buf,\
	size_t nbytes,int flags,const struct sockaddr *to,\
	socklen_t addrlen);
功能:向to結構體指針中指定的 ip,發送 UDP 數據
參數:
	@sockfd     :    套接字
	@buf        :    發送數據緩衝區
	@nbytes     :    發送數據緩衝區的大小
	@flags      :    一般爲 0
	@to         :    指向目的主機地址結構體的指針
	@addrlen    :    to 所指向內容的長度
注意:
	通過 to 和 addrlen 確定目的地址;
	可以發送 0 長度的 UDP 數據包。
返回值:
	成功:發送數據的字符數      失敗: -1

recvfrom : 接受數據

函數原型:
	ssize_t recvfrom(int sockfd, void *buf,\
	size_t nbytes,int flags,struct sockaddr *from,\
	socklen_t *addrlen);
功能:接收 UDP 數據,並將源地址信息保存在 from 指向的結構中
參數:
	@sockfd    :    套接字
	@buf       :    接收數據緩衝區
	@nbytes    :    接收數據緩衝區的大小
	@flags     :    套接字標誌(常爲 0)
	@from      :    源地址結構體指針,用來保存數據的來源
	@addrlen   :    from 所指內容的長度
注意:
	通過 from 和 addrlen 參數存放數據來源信息;
	from 和 addrlen 可以爲 NULL, 表示不保存數據來源。
返回值:
	成功:接收到的字符數      失敗: -1

看完上面的所有函數,可以發現有個參數是未知的,也可以猜測到它包含的成員有很多,那就是struct sockaddr結構體。
這是一個通用套接字地址結構體,用來強轉IPv4結構體。

struct sockaddr
{
    sa_family_t sa_family; // 2 字節
    char sa_data[14] //14 字節
};

IPv4 套接字地址結構體

struct sockaddr_in
{
    sa_family_t sin_family;//2 字節
    in_port_t sin_port;//2 字節
    struct in_addr sin_addr;//4 字節
    char sin_zero[8]//8 字節  必須爲0
};
struct in_addr
{
    in_addr_t s_addr;//4 字節
};

給IPv4地址結構賦值

//定義服務器IPv4地址結構(假設服務器的IP10.0.121.196  port=8080)
    struct sockaddr_in server_addr; 
    //memset(&server_addr,0,sizeof(server_addr));
    bzero(&server_addr,sizeof(server_addr));

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    inet_pton(AF_INET,"10.0.121.196",&server_addr.sin_addr.s_addr)

這其中又出現新的函數:htons、inet_pton,這是一類函數,用來處理大小端帶來的問題,就是在多字節、異構計算機、網絡通信時有的問題。將在下一篇講解。
看都看完了,給個贊,點個關注再走吧,帶你走進奇妙的計算機世界。

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