socket编程方法,概念
“蚓無爪牙之利,筋骨之強,上食埃土,下飲黃泉,用心一也。蟹六跪而二螯,非蛇鱔之穴無可寄托者,用心躁也。”
---------------------------------------------------------------------------------------------------------------------------《勸學(xué)》--荀子
下內(nèi)容參考自:https://blog.csdn.net/qq_26399665/article/details/52421723
哇嗚!現(xiàn)在的網(wǎng)絡(luò)編程,使用TCP/IP協(xié)議的應(yīng)用程序幾乎用的都是socket。
基本概念:
SOCKET(套接字):
Socket的英文原義是“孔”或“插座”,這里指的是?主機的IP地址+主機上的端口號。?
它是網(wǎng)絡(luò)通信過程中端點的抽象表示,包含進(jìn)行網(wǎng)絡(luò)通信必需的五種信息:連接使用的協(xié)議(TCP or UDP),本地主機的IP地址,本地進(jìn)程的協(xié)議端口,遠(yuǎn)地主機的IP地址,遠(yuǎn)地進(jìn)程的協(xié)議端口。
TCP/IP模型的層次: 應(yīng)用層--傳輸層(TCP/UDP)--互聯(lián)網(wǎng)層(IP/ARP/..)--硬件層,??應(yīng)用層?和 傳輸層 通過套接字接口來通信。應(yīng)用層可以同時運行很多服務(wù),每個服務(wù)的進(jìn)程對應(yīng)唯一的端口號,從而可以通過端口號定位到具體的一個服務(wù)。
引用另一篇博客的話:“我們要討論的是網(wǎng)絡(luò)中進(jìn)程之間如何通信?首要解決的問題是如何唯一標(biāo)識一個進(jìn)程,否則通信無從談起!在本地可以通過進(jìn)程PID來唯一標(biāo)識一個進(jìn)程,但是在網(wǎng)絡(luò)中這是行不通的。其實TCP/IP協(xié)議族已經(jīng)幫我們解決了這個問題,h互聯(lián)網(wǎng) 層的??“ip地址”可以唯一標(biāo)識網(wǎng)絡(luò)中的主機,而傳輸層的“UDP/TCP+端口”可以唯一標(biāo)識主機中的應(yīng)用程序(進(jìn)程)。這樣利用三元組(ip地址,協(xié)議,端口)就可以標(biāo)識網(wǎng)絡(luò)的進(jìn)程了,網(wǎng)絡(luò)中的進(jìn)程通信就可以利用這個標(biāo)志與其它進(jìn)程進(jìn)行交互。
socket起源于Unix,s而Unix/Linux基本哲學(xué)之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關(guān)閉close”模式來操作。我的理解就是Socket就是該模式的一個實現(xiàn),socket即是一種特殊的文件,一些socket函數(shù)就是對其進(jìn)行的操作(讀/寫IO、打開、關(guān)閉)”。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
以TCP為例,介紹幾個基本的socket接口函數(shù):
1)socket()函數(shù)
int socket(int domain, int type, int protocol);頭文件#include <sys/socket.h>,linux自帶函數(shù)。
socket函數(shù)對應(yīng)于普通文件的打開操作。普通文件的打開操作返回一個文件描述字,而socket()用于創(chuàng)建一個socket描述符(socket descriptor),它唯一標(biāo)識一個socket。這個socket描述字跟文件描述字一樣,后續(xù)的操作都有用到它,把它作為參數(shù),通過它來進(jìn)行一些讀寫操作。
如果成功就返回生成的SOCKET,如果失敗就返回INVALID_SOCKET(-1).
正如可以給fopen的傳入不同參數(shù)值,以打開不同的文件。創(chuàng)建socket的時候,也可以指定不同的參數(shù)創(chuàng)建不同的socket描述符,socket函數(shù)的三個參數(shù)分別為:?domain參數(shù):協(xié)議族(family),常用的協(xié)議族有,AF_INET、AF_INET6、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協(xié)議族決定了socket的地址類型,在通信中必須采用對應(yīng)的地址,如AF_INET 決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名作為地址。
type參數(shù):指定socket類型,常用的socket類型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等 。?
??1)SOCK_STREAM 提供有序的、可靠的、雙向的和基于連接的字節(jié)流,使用帶外數(shù)據(jù)傳送機制,為Internet地址族使用TCP。(帶外數(shù)據(jù)傳送:使用與普通數(shù)據(jù)不同的通道獨立傳送給用戶,是相連的每一對流套接口間一個邏輯上獨立的傳輸通道。)
SOCK_STREAM類型的套接口為全雙向的字節(jié)流。對于流類套接口,在接收或發(fā)送數(shù)據(jù)前必需處于已連接狀態(tài)。用connect()調(diào)用建立與另一套接口的連接,連接成功后,即可用send()和recv()傳送數(shù)據(jù)。當(dāng)會話結(jié)束后,調(diào)用closesocket()。帶外數(shù)據(jù)根據(jù)規(guī)定用send()和recv()來接收。?
??2)SOCK_DGRAM 支持無連接的、不可靠的和使用固定大小(通常很小)緩沖區(qū)的數(shù)據(jù)報服務(wù),為Internet地址族使用UDP。
protocol參數(shù),指定協(xié)議,常用的協(xié)議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對應(yīng)TCP傳輸協(xié)議、UDP傳輸協(xié)議、STCP傳輸協(xié)議、TIPC傳輸協(xié)議。---都是傳輸層的協(xié)議吧!
參數(shù)protocol為0對應(yīng) IPPROTO_IP,用于接收任何的IP數(shù)據(jù)包,其中的校驗和和協(xié)議分析由程序自己完成。---?????
include/Linux/in.h里的定義:
/* Standard well-defined IP protocols. */
enum {
IPPROTO_IP = 0,? ?/* Dummy protocol for TCP? ?*/
IPPROTO_ICMP = 1,? ?/* Internet Control Message Protocol */
IPPROTO_IGMP = 2,? ?/* Internet Group Management Protocol */
IPPROTO_IPIP = 4,? ?/* IPIP tunnels (older KA9Q tunnels use 94) */
IPPROTO_TCP = 6,? ?/* Transmission Control Protocol */
IPPROTO_EGP = 8,? ?/* Exterior Gateway Protocol? ?*/
IPPROTO_PUP = 12,? ?/* PUP protocol? ? ?*/
IPPROTO_UDP = 17,? ?/* User Datagram Protocol? ?*/
IPPROTO_IDP = 22,? ?/* XNS IDP protocol? ? */
IPPROTO_DCCP = 33,? ?/* Datagram Congestion Control Protocol */
IPPROTO_RSVP = 46,? ?/* RSVP protocol? ? */
IPPROTO_GRE = 47,? ?/* Cisco GRE tunnels (rfc 1701,1702) */
IPPROTO_IPV6 = 41,? ?/* IPv6-in-IPv4 tunnelling? ?*/
IPPROTO_ESP = 50,? ? ? ? ? ? /* Encapsulation Security Payload protocol */
IPPROTO_AH = 51,? ? ? ? ? ? ?/* Authentication Header protocol? ? ? ?*/
IPPROTO_BEETPH = 94,? ? ? ? /* IP option pseudo header for BEET */
IPPROTO_PIM? ? = 103,? ?/* Protocol Independent Multicast */
IPPROTO_COMP? ?= 108,? ? ? ? ? ? ? ? /* Compression Header protocol */
IPPROTO_SCTP? ?= 132,? ?/* Stream Control Transport Protocol */
IPPROTO_UDPLITE = 136, /* UDP-Lite (RFC 3828)? ? */
IPPROTO_RAW = 255,? ?/* Raw IP packets? ? */
IPPROTO_MAX
};
并不是上面的type和protocol可以隨意組合的,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當(dāng)protocol為0時,會自動選擇type類型對應(yīng)的默認(rèn)協(xié)議。
當(dāng)我們調(diào)用socket創(chuàng)建一個socket時,返回的socket描述字它存在于協(xié)議族(address family,AF_XXX)空間中,但沒有一個具體的地址,其實在調(diào)用socket函數(shù)創(chuàng)建socket時,內(nèi)核還并未給socket分配源地址和源端口。
2)bind()函數(shù)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);bind()函數(shù)把一個地址族中的特定地址賦給socket,例如對應(yīng)AF_INET、AF_INET6就是把一個ipv4或ipv6地址和端口號組合賦給socket。
函數(shù)的三個參數(shù)分別為:
????sockfd:即socket描述字,它是通過socket()函數(shù)創(chuàng)建,唯一標(biāo)識一個socket。
????addr:一個const?struct?sockaddr *指針,指向要綁定給sockfd的協(xié)議地址。這個地址結(jié)構(gòu)根據(jù)地址創(chuàng)建socket時的地址協(xié)議族的不????同而不同,如ipv4對應(yīng)的是:?
- struct sockaddr_in {sa_family_t sin_family; /* address family: AF_INET */in_port_t sin_port; /* port in network byte order */struct in_addr sin_addr; /* internet address */ };/* Internet address. */ struct in_addr {uint32_t s_addr; /* address in network byte order */ };
通常服務(wù)器在啟動的時候都會綁定一個固定的地址(如ip地址+端口號),用于提供服務(wù),客戶端就可以通過它來找到這個服務(wù)器;而客戶端就不用指定,有系統(tǒng)自動分配一個端口號和自身的ip地址組合。這就是為什么通常服務(wù)器端在listen之前會調(diào)用bind(),而客戶端就不會調(diào)用,而是在connect()時由系統(tǒng)隨機生成一個。
背景知識:網(wǎng)絡(luò)字節(jié)序與主機字節(jié)序:
主機字節(jié)序就是我們平常說的大端和小端模式:不同的CPU有不同的字節(jié)序類型,這些字節(jié)序是指整數(shù)在內(nèi)存中保存的順序,這個叫做主機序。引用標(biāo)準(zhǔn)的Big-Endian和Little-Endian的定義如下:
a) Little-Endian就是低位字節(jié)排放在內(nèi)存的低地址端,高位字節(jié)排放在內(nèi)存的高地址端。
b) Big-Endian反之。
網(wǎng)絡(luò)字節(jié)序:4個字節(jié)的32 bit值以下面的次序傳輸:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。這種傳輸次序稱作大端字節(jié)序。由于TCP/IP首部中所有的二進(jìn)制整數(shù)在網(wǎng)絡(luò)中傳輸時都要求以這種次序,因此它又稱作網(wǎng)絡(luò)字節(jié)序。字節(jié)序,顧名思義字節(jié)的順序,就是大于一個字節(jié)類型的數(shù)據(jù)在內(nèi)存中的存放順序,一個字節(jié)的數(shù)據(jù)沒有字節(jié)順序的問題了。
3)listen()、connect()函數(shù)
如果作為一個服務(wù)器,在調(diào)用socket()、bind()之后就會調(diào)用listen()來監(jiān)聽這個socket,如果客戶端這時調(diào)用connect()發(fā)出連接請求,服務(wù)器端就會接收到這個請求。int listen(int sockfd,? int backlog);
int connect(int sockfd,? const struct sockaddr *addr,? socklen_t addrlen);--- 客戶端調(diào)用
listen函數(shù)的第一個參數(shù)即為要監(jiān)聽的socket描述字,第二個參數(shù)為相應(yīng)socket可以排隊的最大連接個數(shù)。socket()函數(shù)創(chuàng)建的socket默認(rèn)是一個主動類型的,listen函數(shù)將socket變?yōu)楸粍宇愋偷?#xff0c;等待客戶的連接請求。connect函數(shù)的第一個參數(shù)即為客戶端的socket描述字,第二參數(shù)為服務(wù)器的socket地址,第三個參數(shù)為socket地址的長度。客戶端通過調(diào)用connect函數(shù)來建立與TCP服務(wù)器的連接。
4)accept()函數(shù)
TCP服務(wù)器端依次調(diào)用socket()、bind()、listen()之后,就會監(jiān)聽指定的socket地址(bind()時自己為自己指定的,客戶端向這個地址發(fā)送信息)了。
TCP客戶端依次調(diào)用socket()、connect()之后就向TCP服務(wù)器發(fā)送了一個連接請求。TCP服務(wù)器監(jiān)聽到這個請求之后,就會調(diào)用accept()函數(shù)取接收請求,這樣連接就建立好了。之后就可以開始網(wǎng)絡(luò)I/O操作了,即類同于普通文件的讀寫I/O操作!!
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);accept函數(shù)的第一個參數(shù)為服務(wù)器的socket描述字,第二個參數(shù)為指向struct sockaddr *的指針,用于返回客戶端的協(xié)議地址,第三個參數(shù)為協(xié)議地址的長度。如果accpet成功,那么accept()返回值是由內(nèi)核自動生成的一個全新的描述字,代表與返回客戶的TCP連接。
注意:accept的第一個參數(shù)為服務(wù)器的socket描述字,是服務(wù)器開始調(diào)用socket()函數(shù)生成的,稱為監(jiān)聽socket描述字;而accept函數(shù)返回的是已連接的socket描述字。一個服務(wù)器通常通常僅僅只創(chuàng)建一個監(jiān)聽socket描述字,它在該服務(wù)器的生命周期內(nèi)一直存在。內(nèi)核為每個服務(wù)器進(jìn)程接受的 客戶連接 創(chuàng)建了一個已連接socket描述字,當(dāng)服務(wù)器完成了對某個客戶的服務(wù),相應(yīng)的已連接socket描述字就被關(guān)閉。
5)read()、write()等函數(shù)
萬事具備只欠東風(fēng),至此服務(wù)器與客戶已經(jīng)建立好連接了。可以調(diào)用網(wǎng)絡(luò)I/O進(jìn)行讀寫操作了,即實現(xiàn)了網(wǎng)咯中不同進(jìn)程之間的通信!網(wǎng)絡(luò)I/O操作有下面幾組,對于下面的這些函數(shù),服務(wù)器端和客戶端都都是一樣的!
- read()/write()
- recv()/send()
- readv()/writev()
- recvmsg()/sendmsg()
- recvfrom()/sendto()
舉例子:https://blog.csdn.net/gogor/article/details/5896931
int?send(?SOCKET?s,?const?char?FAR?*buf,?int?len,?int?flags?);?
第一個參數(shù):發(fā)送端套接字描述符;在服務(wù)器端,第一個參數(shù)s為accept函數(shù)返回的socket描述字。
第二個參數(shù)指明一個存放應(yīng)用程序要發(fā)送數(shù)據(jù)的緩沖區(qū);
第三個參數(shù)指明實際要發(fā)送的數(shù)據(jù)的字節(jié)數(shù);
第四個參數(shù)一般置0。int?recv(?SOCKET?s,?????char?FAR?*buf,??????int?len,?????int?flags?????);???
第一個參數(shù)指定接收端套接字描述符;
第二個參數(shù)指明一個緩沖區(qū),該緩沖區(qū)用來存放recv函數(shù)接收到的數(shù)據(jù);
第三個參數(shù)指明buf的長度;
第四個參數(shù)一般置0。6) close()函數(shù)
在服務(wù)器與客戶端建立連接之后,會進(jìn)行一些讀寫操作,完成了讀寫操作就要關(guān)閉相應(yīng)的socket描述字,好比操作完打開的文件要調(diào)用fclose關(guān)閉打開的文件。
#include <unistd.h> int close(int fd);close一個TCP socket的缺省行為時把該socket標(biāo)記為已關(guān)閉,然后立即返回到調(diào)用進(jìn)程。該描述字不能再由調(diào)用進(jìn)程使用,也就是說不能再作為read或write的第一個參數(shù)。
注意:close操作只是使相應(yīng)socket描述字的引用計數(shù)-1,只有當(dāng)引用計數(shù)為0的時候,才會觸發(fā)TCP客戶端向服務(wù)器發(fā)送終止連接請求。------???
一個栗子:
一個簡單的服務(wù)器、客戶端(使用TCP)——服務(wù)器端一直監(jiān)聽本機的6666號端口,如果收到連接請求,將接收請求并接收客戶端發(fā)來的消息;客戶端與服務(wù)器端建立連接并發(fā)送一條消息。
不管有多復(fù)雜的網(wǎng)絡(luò)程序,都使用的這些基本函數(shù)。上面的服務(wù)器使用的是迭代模式的,即只有處理完一個客戶端請求才會去處理下一個客戶端的請求,這樣的服務(wù)器處理能力是很弱的,現(xiàn)實中的服務(wù)器都需要有并發(fā)處理能力!為了需要并發(fā)處理,服務(wù)器需要fork()一個新的進(jìn)程或者線程去處理請求等。服務(wù)器端 復(fù)制代碼 #include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h>#define MAXLINE 4096int main(int argc, char** argv) {int listenfd, connfd;struct sockaddr_in servaddr;char buff[4096];int n;if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){//------------------建立服務(wù)器端的socketprintf("create socket error: %s(errno: %d)\n",strerror(errno),errno);exit(0);}memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(6666);if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){//--指定服務(wù)器的IP地址和端口號printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);exit(0);}if( listen(listenfd, 10) == -1){// ----------------------------------------令服務(wù)器來監(jiān)聽這個socket口!!!printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);exit(0);}printf("======waiting for client's request======\n");while(1){if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){//---等待客戶端發(fā)送數(shù)據(jù)過來,,//accept()返回值是由內(nèi)核自動生成的一個全新的socket描述字,代表客戶端的TCP連接信息printf("accept socket error: %s(errno: %d)",strerror(errno),errno);continue;}n = recv(connfd, buff, MAXLINE, 0);//-------------------------------------接受到的數(shù)據(jù)放到buff中buff[n] = '\0';printf("recv msg from client: %s\n", buff);close(connfd);}close(listenfd); }
客戶端:
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #define MAXLINE 4096 int main(int argc, char** argv) {int sockfd, n;char recvline[4096], sendline[4096];struct sockaddr_in servaddr;if( argc != 2){printf("usage: ./client <ipaddress>\n");exit(0);}if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)//------------建立socket{printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);exit(0);}memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(6666);//inet_pton是一個IP地址轉(zhuǎn)換函數(shù),可以在將IP地址在“點分十進(jìn)制”和“二進(jìn)制整數(shù)”之間轉(zhuǎn)換而且,//inet_pton和inet_ntop這2個函數(shù)能夠處理ipv4和ipv6。算是比較新的函數(shù)if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){printf("inet_pton error for %s\n",argv[1]);exit(0);}if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)//指定服務(wù)端的IP和端口地址給socket{printf("connect error: %s(errno: %d)\n",strerror(errno),errno);exit(0);}printf("send msg to server: \n");fgets(sendline, 4096, stdin);//------------用戶輸入數(shù)據(jù)if( send(sockfd, sendline, strlen(sendline), 0) < 0) //-----------發(fā)送一次數(shù)據(jù)給服務(wù)器端{(lán)printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);exit(0);}close(sockfd);//關(guān)閉socketexit(0);}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
關(guān)于setsockopt函數(shù):
https://www.cnblogs.com/yizhizaiYI/articles/5236221.html
寫服務(wù)器端程序時,將socket IP地址設(shè)置為 INADDR_ANY 的重大意義:
https://www.cnblogs.com/pengdonglin137/p/3309505.html
socket編程總的阻塞和非阻塞的區(qū)別:
https://www.cnblogs.com/h2zZhou/p/7283714.html
REF:
https://blog.csdn.net/qq_26399665/article/details/52421723
轉(zhuǎn)載于:https://www.cnblogs.com/butterflybay/p/10347971.html
總結(jié)
以上是生活随笔為你收集整理的socket编程方法,概念的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringBoot(一)_快速实战搭建
- 下一篇: setlocal启动批处理文件中环境变量