Linux下TCP Socket编程实例
Linux下TCP Socket編程實(shí)例
- Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層
- 1. socket套接字
- 2. 套接字描述符
- 3. 文件描述符和文件指針的區(qū)別
- 4. 基本的SOCKET接口函數(shù)
- 4.1、socket()函數(shù)
- 4.2、bind()函數(shù)
- 4.3、listen()、connect()函數(shù)
- 4.4、accept()函數(shù)
- 4.5、read()、write()等函數(shù)
- 4.6、close()函數(shù)
- Socket編程實(shí)例
原文鏈接:
https://blog.csdn.net/u014426028/article/details/106732669?spm=1001.2101.3001.6650.12&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-12.pc_relevant_aa&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-12.pc_relevant_aa&utm_relevant_index=17
Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層
Socket是什么
1. socket套接字
socket起源于Unix,而Unix/Linux基本哲學(xué)之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關(guān)閉close”模式來操作。Socket就是該模式的一個(gè)實(shí)現(xiàn), socket即是一種特殊的文件,一些socket函數(shù)就是對其進(jìn)行的操作(讀/寫IO、打開、關(guān)閉). 說白了Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層,它是一組接口。在設(shè)計(jì)模式中,Socket其實(shí)就是一個(gè)門面模式,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議。
注意:其實(shí)socket也沒有層的概念,它只是一個(gè)facade設(shè)計(jì)模式的應(yīng)用,讓編程變的更簡單。是一個(gè)軟件抽象層。在網(wǎng)絡(luò)編程中,我們大量用的都是通過socket實(shí)現(xiàn)的。
2. 套接字描述符
其實(shí)就是一個(gè)整數(shù),我們最熟悉的句柄是0、1、2三個(gè),0是標(biāo)準(zhǔn)輸入,1是標(biāo)準(zhǔn)輸出,2是標(biāo)準(zhǔn)錯(cuò)誤輸出。0、1、2是整數(shù)表示的,對應(yīng)的FILE *結(jié)構(gòu)的表示就是stdin、stdout、stderr
套接字API最初是作為UNIX操作系統(tǒng)的一部分而開發(fā)的,所以套接字API與系統(tǒng)的其他I/O設(shè)備集成在一起。特別是,當(dāng)應(yīng)用程序要為因特網(wǎng)通信而創(chuàng)建一個(gè)套接字(socket)時(shí),操作系統(tǒng)就返回一個(gè)小整數(shù)作為描述符(descriptor)來標(biāo)識(shí)這個(gè)套接字。然后,應(yīng)用程序以該描述符作為傳遞參數(shù),通過調(diào)用函數(shù)來完成某種操作(例如通過網(wǎng)絡(luò)傳送數(shù)據(jù)或接收輸入的數(shù)據(jù))。
在許多操作系統(tǒng)中,套接字描述符和其他I/O描述符是集成在一起的,所以應(yīng)用程序可以對文件進(jìn)行套接字I/O或I/O讀/寫操作。
當(dāng)應(yīng)用程序要?jiǎng)?chuàng)建一個(gè)套接字時(shí),操作系統(tǒng)就返回一個(gè)小整數(shù)作為描述符,應(yīng)用程序則使用這個(gè)描述符來引用該套接字需要I/O請求的應(yīng)用程序請求操作系統(tǒng)打開一個(gè)文件。操作系統(tǒng)就創(chuàng)建一個(gè)文件描述符提供給應(yīng)用程序訪問文件。從應(yīng)用程序的角度看,文件描述符是一個(gè)整數(shù),應(yīng)用程序可以用它來讀寫文件。下圖顯示,操作系統(tǒng)如何把文件描述符實(shí)現(xiàn)為一個(gè)指針數(shù)組,這些指針指向內(nèi)部數(shù)據(jù)結(jié)構(gòu)。
對于每個(gè)程序系統(tǒng)都有一張單獨(dú)的表。精確地講,系統(tǒng)為每個(gè)運(yùn)行的進(jìn)程維護(hù)一張單獨(dú)的文件描述符表。當(dāng)進(jìn)程打開一個(gè)文件時(shí),系統(tǒng)把一個(gè)指向此文件內(nèi)部數(shù)據(jù)結(jié)構(gòu)的指針寫入文件描述符表,并把該表的索引值返回給調(diào)用者 。應(yīng)用程序只需記住這個(gè)描述符,并在以后操作該文件時(shí)使用它。操作系統(tǒng)把該描述符作為索引訪問進(jìn)程描述符表,通過指針找到保存該文件所有的信息的數(shù)據(jù)結(jié)構(gòu)。
** 針對套接字的系統(tǒng)數(shù)據(jù)結(jié)構(gòu):**
1)、套接字API里有個(gè)函數(shù)socket,它就是用來創(chuàng)建一個(gè)套接字。套接字設(shè)計(jì)的總體思路是,單個(gè)系統(tǒng)調(diào)用就可以創(chuàng)建任何套接字,因?yàn)樘捉幼质窍喈?dāng)籠統(tǒng)的。一旦套接字創(chuàng)建后,應(yīng)用程序還需要調(diào)用其他函數(shù)來指定具體細(xì)節(jié)。例如調(diào)用socket將創(chuàng)建一個(gè)新的描述符條目:
2)、雖然套接字的內(nèi)部數(shù)據(jù)結(jié)構(gòu)包含很多字段,但是系統(tǒng)創(chuàng)建套接字后,大多數(shù)字字段沒有填寫。應(yīng)用程序創(chuàng)建套接字后在該套接字可以使用之前,必須調(diào)用其他的過程來填充這些字段。
3. 文件描述符和文件指針的區(qū)別
文件描述符:在linux系統(tǒng)中打開文件就會(huì)獲得文件描述符,它是個(gè)很小的正整數(shù)。每個(gè)進(jìn)程在PCB(Process Control Block)中保存著一份文件描述符表,文件描述符就是這個(gè)表的索引,每個(gè)表項(xiàng)都有一個(gè)指向已打開文件的指針。
文件指針:C語言中使用文件指針做為I/O的句柄。文件指針指向進(jìn)程用戶區(qū)中的一個(gè)被稱為FILE結(jié)構(gòu)的數(shù)據(jù)結(jié)構(gòu)。FILE結(jié)構(gòu)包括一個(gè)緩沖區(qū)和一個(gè)文件描述符。而文件描述符是文件描述符表的一個(gè)索引,因此從某種意義上說文件指針就是句柄的句柄(在Windows系統(tǒng)上,文件描述符被稱作文件句柄)。
4. 基本的SOCKET接口函數(shù)
在生活中,A要電話給B,A撥號(hào),B聽到電話鈴聲后提起電話,這時(shí)A和B就建立起了連接,A和B就可以講話了。等交流結(jié)束,掛斷電話結(jié)束此次交談。 打電話很簡單解釋了這工作原理:“open—write/read—close”模式。
服務(wù)器端先初始化Socket,然后與端口綁定(bind),對端口進(jìn)行監(jiān)聽(listen),調(diào)用accept阻塞,等待客戶端連接。在這時(shí)如果有個(gè)客戶端初始化一個(gè)Socket,然后連接服務(wù)器(connect),如果連接成功,這時(shí)客戶端與服務(wù)器端的連接就建立了。客戶端發(fā)送數(shù)據(jù)請求,服務(wù)器端接收請求并處理請求,然后把回應(yīng)數(shù)據(jù)發(fā)送給客戶端,客戶端讀取數(shù)據(jù),最后關(guān)閉連接,一次交互結(jié)束。 這些接口的實(shí)現(xiàn)都是內(nèi)核來完成。具體如何實(shí)現(xiàn),可以看看linux的內(nèi)核
4.1、socket()函數(shù)
int socket(int protofamily, int type, int protocol);//返回sockfd
sockfd是描述符。
socket函數(shù)對應(yīng)于普通文件的打開操作。普通文件的打開操作返回一個(gè)文件描述字,而socket()用于創(chuàng)建一個(gè)socket描述符(socket descriptor),它唯一標(biāo)識(shí)一個(gè)socket。這個(gè)socket描述字跟文件描述字一樣,后續(xù)的操作都有用到它,把它作為參數(shù),通過它來進(jìn)行一些讀寫操作。
正如可以給fopen的傳入不同參數(shù)值,以打開不同的文件。創(chuàng)建socket的時(shí)候,也可以指定不同的參數(shù)創(chuàng)建不同的socket描述符,socket函數(shù)的三個(gè)參數(shù)分別為:
protofamily:即協(xié)議域,又稱為協(xié)議族(family)。常用的協(xié)議族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協(xié)議族決定了socket的地址類型,在通信中必須采用對應(yīng)的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(hào)(16位的)的組合、AF_UNIX決定了要用一個(gè)絕對路徑名作為地址。
type:指定socket類型。常用的socket類型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的類型有哪些?)。
protocol:故名思意,就是指定協(xié)議。常用的協(xié)議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對應(yīng)TCP傳輸協(xié)議、UDP傳輸協(xié)議、STCP傳輸協(xié)議、TIPC傳輸協(xié)議(這個(gè)協(xié)議我將會(huì)單獨(dú)開篇討論!)。
注意:并不是上面的type和protocol可以隨意組合的,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當(dāng)protocol為0時(shí),會(huì)自動(dòng)選擇type類型對應(yīng)的默認(rèn)協(xié)議。
當(dāng)我們調(diào)用socket創(chuàng)建一個(gè)socket時(shí),返回的socket描述字它存在于協(xié)議族(address family,AF_XXX)空間中,但沒有一個(gè)具體的地址。如果想要給它賦值一個(gè)地址,就必須調(diào)用bind()函數(shù),否則就當(dāng)調(diào)用connect()、listen()時(shí)系統(tǒng)會(huì)自動(dòng)隨機(jī)分配一個(gè)端口。
4.2、bind()函數(shù)
正如上面所說bind()函數(shù)把一個(gè)地址族中的特定地址賦給socket。例如對應(yīng)AF_INET、AF_INET6就是把一個(gè)ipv4或ipv6地址和端口號(hào)組合賦給socket。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函數(shù)的三個(gè)參數(shù)分別為:
sockfd:即socket描述字,它是通過socket()函數(shù)創(chuàng)建了,唯一標(biāo)識(shí)一個(gè)socket。bind()函數(shù)就是將給這個(gè)描述字綁定一個(gè)名字。
addr:一個(gè)const struct sockaddr *指針,指向要綁定給sockfd的協(xié)議地址。這個(gè)地址結(jié)構(gòu)根據(jù)地址創(chuàng)建socket時(shí)的地址協(xié)議族的不同而不同,如ipv4對應(yīng)的是:
structsockaddr_in{sa_family_tsin_family;/* address family: AF_INET /in_port_tsin_port;/ port in network byte order /structin_addrsin_addr;/ internet address /};/ Internet address. /structin_addr{uint32_ts_addr;/ address in network byte order /};ipv6對應(yīng)的是:structsockaddr_in6{sa_family_tsin6_family;/ AF_INET6 /in_port_tsin6_port;/ port number /uint32_tsin6_flowinfo;/ IPv6 flow information /structin6_addrsin6_addr;/ IPv6 address /uint32_tsin6_scope_id;/ Scope ID (new in 2.4) /};structin6_addr{unsignedchars6_addr[16];/ IPv6 address /};Unix域?qū)?yīng)的是:#defineUNIX_PATH_MAX 108structsockaddr_un{sa_family_tsun_family;/ AF_UNIX /charsun_path[UNIX_PATH_MAX];/ pathname */};
addrlen:對應(yīng)的是地址的長度。
通常服務(wù)器在啟動(dòng)的時(shí)候都會(huì)綁定一個(gè)眾所周知的地址(如ip地址+端口號(hào)),用于提供服務(wù),客戶就可以通過它來接連服務(wù)器;而客戶端就不用指定,有系統(tǒng)自動(dòng)分配一個(gè)端口號(hào)和自身的ip地址組合。這就是為什么通常服務(wù)器端在listen之前會(huì)調(diào)用bind(),而客戶端就不會(huì)調(diào)用,而是在connect()時(shí)由系統(tǒng)隨機(jī)生成一個(gè)。
網(wǎng)絡(luò)字節(jié)序與主機(jī)字節(jié)序
主機(jī)字節(jié)序就是我們平常說的大端和小端模式:不同的CPU有不同的字節(jié)序類型,這些字節(jié)序是指整數(shù)在內(nèi)存中保存的順序,這個(gè)叫做主機(jī)序。引用標(biāo)準(zhǔn)的Big-Endian和Little-Endian的定義如下:
a) Little-Endian就是低位字節(jié)排放在內(nèi)存的低地址端,高位字節(jié)排放在內(nèi)存的高地址端。
b) Big-Endian就是高位字節(jié)排放在內(nèi)存的低地址端,低位字節(jié)排放在內(nèi)存的高地址端。
網(wǎng)絡(luò)字節(jié)序:4個(gè)字節(jié)的32 bit值以下面的次序傳輸:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。這種傳輸次序稱作大端字節(jié)序。由于TCP/IP首部中所有的二進(jìn)制整數(shù)在網(wǎng)絡(luò)中傳輸時(shí)都要求以這種次序,因此它又稱作網(wǎng)絡(luò)字節(jié)序。字節(jié)序,顧名思義字節(jié)的順序,就是大于一個(gè)字節(jié)類型的數(shù)據(jù)在內(nèi)存中的存放順序,一個(gè)字節(jié)的數(shù)據(jù)沒有順序的問題了。
所以:在將一個(gè)地址綁定到socket的時(shí)候,請先將主機(jī)字節(jié)序轉(zhuǎn)換成為網(wǎng)絡(luò)字節(jié)序,而不要假定主機(jī)字節(jié)序跟網(wǎng)絡(luò)字節(jié)序一樣使用的是Big-Endian。由于這個(gè)問題曾引發(fā)過血案!公司項(xiàng)目代碼中由于存在這個(gè)問題,導(dǎo)致了很多莫名其妙的問題,所以請謹(jǐn)記對主機(jī)字節(jié)序不要做任何假定,務(wù)必將其轉(zhuǎn)化為網(wǎng)絡(luò)字節(jié)序再賦給socket。
4.3、listen()、connect()函數(shù)
如果作為一個(gè)服務(wù)器,在調(diào)用socket()、bind()之后就會(huì)調(diào)用listen()來監(jiān)聽這個(gè)socket,如果客戶端這時(shí)調(diào)用connect()發(fā)出連接請求,服務(wù)器端就會(huì)接收到這個(gè)請求。
int listen(int sockfd, int backlog);int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
listen函數(shù)的第一個(gè)參數(shù)即為要監(jiān)聽的socket描述字,第二個(gè)參數(shù)為相應(yīng)socket可以排隊(duì)的最大連接個(gè)數(shù)。socket()函數(shù)創(chuàng)建的socket默認(rèn)是一個(gè)主動(dòng)類型的,listen函數(shù)將socket變?yōu)楸粍?dòng)類型的,等待客戶的連接請求。
connect函數(shù)的第一個(gè)參數(shù)即為客戶端的socket描述字,第二參數(shù)為服務(wù)器的socket地址,第三個(gè)參數(shù)為socket地址的長度。客戶端通過調(diào)用connect函數(shù)來建立與TCP服務(wù)器的連接。
4.4、accept()函數(shù)
TCP服務(wù)器端依次調(diào)用socket()、bind()、listen()之后,就會(huì)監(jiān)聽指定的socket地址了。TCP客戶端依次調(diào)用socket()、connect()之后就向TCP服務(wù)器發(fā)送了一個(gè)連接請求。TCP服務(wù)器監(jiān)聽到這個(gè)請求之后,就會(huì)調(diào)用accept()函數(shù)取接收請求,這樣連接就建立好了。之后就可以開始網(wǎng)絡(luò)I/O操作了,即類同于普通文件的讀寫I/O操作。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回連接connect_fd
參數(shù)sockfd
參數(shù)sockfd就是上面解釋中的監(jiān)聽套接字,這個(gè)套接字用來監(jiān)聽一個(gè)端口,當(dāng)有一個(gè)客戶與服務(wù)器連接時(shí),它使用這個(gè)一個(gè)端口號(hào),而此時(shí)這個(gè)端口號(hào)正與這個(gè)套接字關(guān)聯(lián)。當(dāng)然客戶不知道套接字這些細(xì)節(jié),它只知道一個(gè)地址和一個(gè)端口號(hào)。
參數(shù)addr
這是一個(gè)結(jié)果參數(shù),它用來接受一個(gè)返回值,這返回值指定客戶端的地址,當(dāng)然這個(gè)地址是通過某個(gè)地址結(jié)構(gòu)來描述的,用戶應(yīng)該知道這一個(gè)什么樣的地址結(jié)構(gòu)。如果對客戶的地址不感興趣,那么可以把這個(gè)值設(shè)置為NULL。
參數(shù)len
如同大家所認(rèn)為的,它也是結(jié)果的參數(shù),用來接受上述addr的結(jié)構(gòu)的大小的,它指明addr結(jié)構(gòu)所占有的字節(jié)個(gè)數(shù)。同樣的,它也可以被設(shè)置為NULL。
如果accept成功返回,則服務(wù)器與客戶已經(jīng)正確建立連接了,此時(shí)服務(wù)器通過accept返回的套接字來完成與客戶的通信。
注意:
accept默認(rèn)會(huì)阻塞進(jìn)程,直到有一個(gè)客戶連接建立后返回,它返回的是一個(gè)新可用的套接字,這個(gè)套接字是連接套接字。
此時(shí)我們需要區(qū)分兩種套接字,
監(jiān)聽套接字: 監(jiān)聽套接字正如accept的參數(shù)sockfd,它是監(jiān)聽套接字,在調(diào)用listen函數(shù)之后,是服務(wù)器開始調(diào)用socket()函數(shù)生成的,稱為監(jiān)聽socket描述字(監(jiān)聽套接字)
連接套接字:一個(gè)套接字會(huì)從主動(dòng)連接的套接字變身為一個(gè)監(jiān)聽套接字;而accept函數(shù)返回的是已連接socket描述字(一個(gè)連接套接字),它代表著一個(gè)網(wǎng)絡(luò)已經(jīng)存在的點(diǎn)點(diǎn)連接。
一個(gè)服務(wù)器通常通常僅僅只創(chuàng)建一個(gè)監(jiān)聽socket描述字,它在該服務(wù)器的生命周期內(nèi)一直存在。內(nèi)核為每個(gè)由服務(wù)器進(jìn)程接受的客戶連接創(chuàng)建了一個(gè)已連接socket描述字,當(dāng)服務(wù)器完成了對某個(gè)客戶的服務(wù),相應(yīng)的已連接socket描述字就被關(guān)閉。
自然要問的是:為什么要有兩種套接字?原因很簡單,如果使用一個(gè)描述字的話,那么它的功能太多,使得使用很不直觀,同時(shí)在內(nèi)核確實(shí)產(chǎn)生了一個(gè)這樣的新的描述字。
連接套接字socketfd_new 并沒有占用新的端口與客戶端通信,依然使用的是與監(jiān)聽套接字socketfd一樣的端口號(hào)
4.5、read()、write()等函數(shù)
萬事具備只欠東風(fēng),至此服務(wù)器與客戶已經(jīng)建立好連接了。可以調(diào)用網(wǎng)絡(luò)I/O進(jìn)行讀寫操作了,即實(shí)現(xiàn)了網(wǎng)咯中不同進(jìn)程之間的通信!網(wǎng)絡(luò)I/O操作有下面幾組:
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
我推薦使用recvmsg()/sendmsg()函數(shù),這兩個(gè)函數(shù)是最通用的I/O函數(shù),實(shí)際上可以把上面的其它函數(shù)都替換成這兩個(gè)函數(shù)。它們的聲明如下:
#includessize_tread(intfd,void*buf,size_tcount);ssize_twrite(intfd,constvoid*buf,size_tcount);#include#includessize_tsend(intsockfd,constvoid*buf,size_tlen,intflags);ssize_trecv(intsockfd,void*buf,size_tlen,intflags);ssize_tsendto(intsockfd,constvoid*buf,size_tlen,intflags,conststruct sockaddr *dest_addr,socklen_taddrlen);ssize_trecvfrom(intsockfd,void*buf,size_tlen,intflags, struct sockaddr *src_addr,socklen_t*addrlen);ssize_tsendmsg(intsockfd,conststruct msghdr *msg,intflags);ssize_trecvmsg(intsockfd, struct msghdr *msg,intflags);read函數(shù)是負(fù)責(zé)從fd中讀取內(nèi)容.當(dāng)讀成功時(shí),read返回實(shí)際所讀的字節(jié)數(shù),如果返回的值是0表示已經(jīng)讀到文件的結(jié)束了,小于0表示出現(xiàn)了錯(cuò)誤。如果錯(cuò)誤為EINTR說明讀是由中斷引起的,如果是ECONNREST表示網(wǎng)絡(luò)連接出了問題。
write函數(shù)將buf中的nbytes字節(jié)內(nèi)容寫入文件描述符fd.成功時(shí)返回寫的字節(jié)數(shù)。失敗時(shí)返回-1,并設(shè)置errno變量。 在網(wǎng)絡(luò)程序中,當(dāng)我們向套接字文件描述符寫時(shí)有倆種可能。1)write的返回值大于0,表示寫了部分或者是全部的數(shù)據(jù)。2)返回的值小于0,此時(shí)出現(xiàn)了錯(cuò)誤。我們要根據(jù)錯(cuò)誤類型來處理。如果錯(cuò)誤為EINTR表示在寫的時(shí)候出現(xiàn)了中斷錯(cuò)誤。如果為EPIPE表示網(wǎng)絡(luò)連接出現(xiàn)了問題(對方已經(jīng)關(guān)閉了連接)。
其它的我就不一一介紹這幾對I/O函數(shù)了,具體參見man文檔或者baidu、Google,下面的例子中將使用到send/recv。
4.6、close()函數(shù)
在服務(wù)器與客戶端建立連接之后,會(huì)進(jìn)行一些讀寫操作,完成了讀寫操作就要關(guān)閉相應(yīng)的socket描述字,好比操作完打開的文件要調(diào)用fclose關(guān)閉打開的文件。
int close(int fd);
close一個(gè)TCP socket的缺省行為時(shí)把該socket標(biāo)記為以關(guān)閉,然后立即返回到調(diào)用進(jìn)程。該描述字不能再由調(diào)用進(jìn)程使用,也就是說不能再作為read或write的第一個(gè)參數(shù)。
注意:close操作只是使相應(yīng)socket描述字的引用計(jì)數(shù)-1,只有當(dāng)引用計(jì)數(shù)為0的時(shí)候,才會(huì)觸發(fā)TCP客戶端向服務(wù)器發(fā)送終止連接請求。
Socket編程實(shí)例
服務(wù)器端:一直監(jiān)聽本機(jī)的8888號(hào)端口,如果收到連接請求,將接收請求并接收客戶端發(fā)來的消息,并向客戶端返回消息。
服務(wù)器代碼
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #define PORT 50000 /*偵聽端口地址*/ #define BACKLOG 10 /*偵聽隊(duì)列長度*//*服務(wù)器對客戶端的處理*/ void process_conn_server(int s) {ssize_t size = 0;char buffer[1024]; /*數(shù)據(jù)的緩沖區(qū)*/for(;;){ /*循環(huán)處理過程*/ size = read(s, buffer, 1024); /*從套接字中讀取數(shù)據(jù)放到 緩沖區(qū)buffer中*/if(size == 0){ /*沒有數(shù)據(jù)*/return; }/*構(gòu)建響應(yīng)字符,為接收到客戶端字節(jié)的數(shù)量*/buffer[size] = '\0';printf("client send:%s\r\n",buffer);//sprintf(buffer, "%d bytes received\n", size);write(s, buffer, strlen(buffer)+1);/*發(fā)給客戶端*/} }int main(int argc, char *argv[]) {int ss,sc; /*ss為服務(wù)器的socket描述符,sc為客戶端的socket描述符*/struct sockaddr_in server_addr; /*服務(wù)器地址結(jié)構(gòu)*/struct sockaddr_in client_addr; /*客戶端地址結(jié)構(gòu)*/int err; /*返回值*/pid_t pid; /*分叉的進(jìn)行ID*//*建立一個(gè)流式套接字*/ss = socket(AF_INET, SOCK_STREAM, 0);if(ss < 0){ /*出錯(cuò)*/printf("socket error\n");return -1; }/*設(shè)置服務(wù)器地址*/bzero(&server_addr, sizeof(server_addr)); /*清零*/server_addr.sin_family = AF_INET; /*協(xié)議族*/server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*本地地址*/server_addr.sin_port = htons(PORT); /*服務(wù)器端口*//*綁定地址結(jié)構(gòu)到套接字描述符*/err = bind(ss, (struct sockaddr*)&server_addr, sizeof(server_addr));if(err < 0){/*出錯(cuò)*/printf("bind error\n");return -1; }/*設(shè)置偵聽*/err = listen(ss, BACKLOG);if(err < 0){ /*出錯(cuò)*/printf("listen error\n");return -1; }/*主循環(huán)過程*/for(;;) {socklen_t addrlen = sizeof(struct sockaddr);sc = accept(ss, (struct sockaddr*)&client_addr, &addrlen); /*接收客戶端連接*/if(sc < 0){ /*出錯(cuò)*/continue; /*結(jié)束本次循環(huán)*/} /*建立一個(gè)新的進(jìn)程處理到來的連接*/pid = fork(); /*分叉進(jìn)程*/if( pid == 0 ){ /*子進(jìn)程中*/process_conn_server(sc); /*處理連接*/close(ss); /*在子進(jìn)程中關(guān)閉服務(wù)器的偵聽*/}else{close(sc); /*在父進(jìn)程中關(guān)閉客戶端的連接*/}} }Linux串口編程短信篇(三) ——— AT指令封裝實(shí)現(xiàn)Check系列函數(shù)及SMS函數(shù)封裝
https://blog.csdn.net/weixin_45121946/article/details/107321879
總結(jié)
以上是生活随笔為你收集整理的Linux下TCP Socket编程实例的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VB程序的脱壳技巧
- 下一篇: linux系统 32位 64位 jdk下