linux網路通訊-套接字
socket程式設計是網路常用的程式設計,我們通過在網路中建立socket關鍵字來實現網路間的通訊。
1.TCP/IP協議 先來簡單瞭解一下TCP/IP協議:
iso7層架構
應用層 | 應用層不僅要提供應用程序所需要資訊交換和遠端操作,而且還要作為應用程序的使用者代理,完成一些為進行語義上有意義的資訊交換所必須的功能。 |
---|---|
表示層 | 用於處理兩個通訊系統間資訊交換的表示方式,它包括資料格式變換、資料加密與解密、資料壓縮與恢復等功能。 |
會話層 | 組織同步的兩個會話使用者之間的對話,並管理資料的交換。 |
傳輸層 | 向用戶提供可靠的端到端服務,透明地傳送報文。 |
網路層 | 通過執行路由選擇演算法,為報文分組通過通訊子網選擇最適當的路徑。 |
資料鏈層 | 在物理層提供位元流傳輸服務的基礎上,在通訊實體之間建立資料鏈路連線,傳送以幀為單位的資料 |
物理層 | 物理層的主要功能是利用物理傳輸介質為資料鏈路層提供物理連線,以透明地傳送位元流。 |
不同於iso7層架構,TCP/IP協議分為4層
應用層 | TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等 |
---|---|
傳輸層 | TCP,UDP |
網路層 | IP,ICMP,OSPF,EIGRP,IGMP |
資料鏈層 | SLIP,CSLIP,PPP,MTU |
在tcp/ip協議中,tcp通過三次握手建立起一個tcp的連結 第一次握手:客戶端嘗試連線伺服器,向伺服器傳送syn包,syn=j,客戶端進入SYN_SEND狀態等待伺服器確認。 第二次握手:伺服器接收客戶端syn包並確認(ack=j+1),同時向客戶端傳送一個SYN包(syn=k),即SYN+ACK包,此時伺服器進入SYN_RECV狀態 第三次握手:客戶端收到伺服器的SYN+ACK包,向伺服器傳送確認包ACK(ack=k+1),此包傳送完畢,客戶端和伺服器進入ESTABLISHED狀態,完成三次握手。
socket就在應用程式的傳輸層和應用層之間,設計了一個socket抽象層,傳輸層的底一層的服務提供給socket抽象層,socket抽象層再提供給應用層。
2.socket程式設計 (1)套接字的建立和銷燬
使用socket函式建立一個套接字
#include<sys/socket.h>
int socket(int domain,int type,int protocol)
引數domain(域)確定通訊的特性
域 | 描述 |
---|---|
AF_INET | IPV4因特網域 |
AF_INET6 | IPV6因特網域 |
AF_UNIX | UNIX域 |
AF_UPSPEC | 未指定 |
引數type確定套接字的型別
型別 | 描述 |
---|---|
SOCK_DGRAM | 固定長度的,無連線的,不可靠的報文傳遞 |
SOCK_RAM | IP協議的資料包介面 |
SOCK_SEQPACKET | 固定長度的,有序的,可靠的,面向連線的報文傳遞 |
SOCK_STREAM | 有序的,可靠的,雙向的,面向連線的的位元組流 |
引數protocol通常是0,表示為給定的域和套接字型別選定預設的協議。
呼叫套接字與呼叫open函式相似,均可獲得用於I/O的檔案描述符。當不再需要該檔案符時,呼叫close函式來關閉對檔案和或套接字的訪問。
套接字的通訊是雙向的,可以採用函式shutdown來禁止一個套接字的I/O。
#include<sys/socket.h>
int shutdown(int sockfd,int how);
返回值:成功返回0,出錯返回-1
引數how:SHUT_RD關閉讀端,SHUT_WR關閉寫端
(2).位元組序 大端位元組序:最大位元組地址出現最低有效位元組 小端位元組序:最小位元組地址在最低有效位元組 對於32位整數0X04030201,最高有效位元組為04,最低為01。採用大端,則最大位元組地址包含01。反之則最小位元組地址包含01。
網路協議指定了位元組序,對於TCP/IP協議棧,使用大端位元組序。 應用程式需要在處理器位元組序與網路位元組序之間轉換它們。對於TCP/IP應用程式,有四個用來轉換的函式。
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostint32); //host to net 將主機位元組序轉換為32位網路位元組序
uint16_t htons(uint16_t hostint16); //host to net 將主機位元組序轉換為16位網路位元組序
uint32_t ntohl(uint32_t netint32); //net to host 將網路位元組序轉換為32位主機位元組序
uint16_t ntohs(uint16_t netint16); //net to host 將網路位元組序轉換為16位主機位元組序
(3)地址格式 在linux下,套接字地址用結構體sockaddr_in表示
struct sockaddr_in {
short sin_family; //地址型別,ipv4或ipv6
unsigned short sin_port; //埠
struct in_addr sin_addr; //ip地址
unsigned char sin_zero[8]; //無意義,用來補全16位位元組
};
(4)將套接字與地址關聯 給伺服器器關聯上一個眾所周知的地址。 使用函式bind來關聯地址和套接字
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t len)
返回值:成功返回0,失敗返回-1
引數sockfd:是由socket()呼叫返回的並且未作連線的套接字描述符(套接字號)。引數addr :是賦給套接字s的本地地址(名字),其長度可變,結構隨通訊域的不同而不同。 引數len:表明了addr的長度。
(5)建立套接字連線 函式connect
#include<sys/socket.h>
int connect(int sockfd,const struct sockaddr *addr,socklen_t len)
返回值:成功返回0,失敗返回-1
引數sockfd:是本地套接字描述符 引數addr是伺服器地址 引數len:是伺服器地址長度
函式listen 伺服器呼叫listen函式來宣告它願意接受請求
#include<sys/socket.h>
int listen(int sockfd,int backlog)
返回值:成功返回0,出錯返回-1
引數sockfd:標識一個本地已建立、尚未連線的套接字號,伺服器願意從它上面接收請求。 引數backlog:提示系統該程序所要入隊的未完成連線請求數量。佇列滿,伺服器就會拒絕多餘的連線請求。
函式accept 一旦伺服器呼叫了lieten,使用的套接字就能接受連線請求。使用accept來獲得連線請求,並建立連線。
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr *restrict addr,socklen_t *restrict len)
返回值:成功返回描述符,出錯返回-1
引數sockfd:本地套接字描述符 引數addr:客戶端套接字地址
(6)資料傳輸 有6個函式用於資料傳輸,3個傳送,3個接受。這裡我就只寫兩個 函式send
#include<sys/socket.h>
ssize_t send(int sockfd,const void *buf,size_t nbytes,int flags);
返回值:成功返回位元組數,失敗返回-1
send函式與write函式相似,多了一個flags引數 總結一下這些flag
MSG_CONFIRM | 提供鏈路層反饋以保持地址對映有效 |
---|---|
MSG_DONTROUTE | 勿將資料包路由出本地網路 |
MSG_DONTWAIT | 允許非阻塞操作 |
MSG_EOR | 如果協議支援,標記記錄結束 |
MSG_MORE | 延遲傳送資料包,允許寫更多資料 |
MSG_NOSIGNAL | 在寫無連線的套接字時不產生SIGPIPE訊號 |
MSG_OOB | 如果協議支援,傳送帶外資料 |
函式recv
#include<sys/socket.h>
ssize_t recv(int sockfd,void *buf,size_t nbytes,int flags)
與read函式相似,多了一個flags控制標誌
MSG_GMSG_CLOEXEC | 位unix域套接字上接收的檔案描述符設定執行時關閉標誌 |
---|---|
MSG_DONTWAIT | 啟用非阻塞操作 |
MSG_ERR | 接收錯誤訊息作為輔助資料 |
MSG_OOB | 如果協議支援,獲取帶外資料 |
MSG_PEEK | 返回資料包內容,而不真正取走資料包 |
MSG_TRUNC | 即使資料包被截斷,也返回資料包的長度 |
MSG_WAIT | 等待直到所有資料可用 |
伺服器例程
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
int main()
{
int sockfd;
int clitfd
struct sockaddr_in serv_addr;
struct sockaddr_in clint_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
char buffer[]="hello I'm a server"
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) //建立伺服器套接字
{
fprintf(stderr,"socket failed\n");
exit(1);
}
serv_addr.sin_family=AF_INET; //初始化伺服器IP,埠號
serv_addr.sin_addr.s_addr=htonl(127.0.0.1);
serv_addr.sin_port=htons(8080);
bind(sockfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)); //將伺服器與地址繫結
listen(sockfd,10); //監聽
clitfd=accept(sockfd,(struct sockaddr*)&clint_addr,(socklen_t)(sizeof(clint_addr))); //接受請求,返回客戶端描述符
send(clitfd,buffer,sizeof(buffer),0); //向客戶端寫資料
close(sockfd); //關閉套接字描述符
close(clitfd);
return 0;
}
客戶端例程
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
int main()
{
struct sockaddr_in clint_addr;
struct sockaddr_in serv_addr;
int clintfd;
char buffer[100];
memset(&clint_addr, 0, sizeof(clint_addr));
if((clintfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"socket failed\n");
exit(1);
}
clint_addr.sin_family=AF_INET;
clint_addr.sin_addr.s_addr = htonl(127.0.0.1);
clint_addr.sin_port = htons(1234);
if((connect(clintfd,(struct sockaddr_in*)&serv_addr,sizeof(serv_addr)))==-1)
{
fprintf(stderr,"connect failed\n");
exit(1);
}
recv(clintfd,buffer,sizeof(buffer),0)
close(clintfd);
return 0;
}