首先看一下上一篇三次握手四次揮手文章中提到的原理圖。
其中的read對應的就是recv函數,write對應的就是send函數。
步入正題,函數的使用:
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,這是一類函數,用來處理大小端帶來的問題,就是在多字節、異構計算機、網絡通信時有的問題。將在下一篇講解。
看都看完了,給個贊,點個關注再走吧,帶你走進奇妙的計算機世界。