1. 程式人生 > >linux網路通訊-套接字

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;
	
}