前言
我們做前端開(kāi)發(fā)主要用http/https請(qǐng)求勉躺,這種請(qǐng)求從數(shù)據(jù)更新角度是單向的瘫怜,即用戶發(fā)起請(qǐng)求才能獲取到最新數(shù)據(jù)扇售。但有時(shí)候,一些狀態(tài)和數(shù)據(jù)的變更要及時(shí)推送到前端膝藕。例如O2O行業(yè)逮京,消費(fèi)者下定單 -> O2O公司接受到訂單 -> 送外賣小哥即時(shí)收到訂單-> 消費(fèi)者實(shí)時(shí)收到外賣小哥和自己的距離。
其中束莫,后兩步要即時(shí)收到信息,就得利用 Socket編程保持長(zhǎng)連接草描。再比如览绿,消息推送,語(yǔ)音聊天等穗慕。
注意:
HTTP也可以建立長(zhǎng)連接的饿敲,使用Connection:keep-alive,HTTP 1.1默認(rèn)進(jìn)行持久連接逛绵。HTTP1.1和HTTP1.0相比較而言怀各,最大的區(qū)別就是增加了持久連接支持(貌似最新的 http1.0 可以顯示的指定 keep-alive),但還是無(wú)狀態(tài)的,或者說(shuō)是不可以信任的术浪。
1瓢对、網(wǎng)絡(luò)中進(jìn)程之間如何通信?
本地的進(jìn)程間通信(IPC)有很多種方式胰苏,但可以總結(jié)為下面4類:
- 消息傳遞(管道硕蛹、FIFO、消息隊(duì)列)
- 同步(互斥量硕并、條件變量法焰、讀寫鎖、文件和寫記錄鎖倔毙、信號(hào)量)
- 共享內(nèi)存(匿名的和具名的)
- 遠(yuǎn)程過(guò)程調(diào)用(Solaris門和Sun RPC)
網(wǎng)絡(luò)中進(jìn)程之間如何通信埃仪?
首要解決的問(wèn)題是如何唯一標(biāo)識(shí)一個(gè)進(jìn)程,否則通信無(wú)從談起陕赃!在本地可以通過(guò)進(jìn)程PID來(lái)唯一標(biāo)識(shí)一個(gè)進(jìn)程卵蛉,但是在網(wǎng)絡(luò)中這是行不通的。其實(shí)TCP/IP協(xié)議族已經(jīng)幫我們解決了這個(gè)問(wèn)題凯正,網(wǎng)絡(luò)層的“ip地址”可以唯一標(biāo)識(shí)網(wǎng)絡(luò)中的主機(jī)毙玻,而傳輸層的“協(xié)議+端口”可以唯一標(biāo)識(shí)主機(jī)中的應(yīng)用程序(進(jìn)程)。這樣利用三元組(ip地址廊散,協(xié)議桑滩,端口)就可以標(biāo)識(shí)網(wǎng)絡(luò)的進(jìn)程了,網(wǎng)絡(luò)中的進(jìn)程通信就可以利用這個(gè)標(biāo)志與其它進(jìn)程進(jìn)行交互。
使用TCP/IP協(xié)議的應(yīng)用程序通常采用應(yīng)用編程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已經(jīng)被淘汰)运准,來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)進(jìn)程之間的通信幌氮。
就目前而言,幾乎所有的應(yīng)用程序都是采用socket胁澳,而現(xiàn)在又是網(wǎng)絡(luò)時(shí)代该互,網(wǎng)絡(luò)中進(jìn)程通信是無(wú)處不在,這就是我為什么說(shuō)“一切皆socket”韭畸。
2. Socket是什么
2.1 socket套接字:
socket起源于Unix宇智,而Unix/Linux基本哲學(xué)之一就是“一切皆文件”,都可以用“打開(kāi)open –> 讀寫write/read –> 關(guān)閉close”模式來(lái)操作胰丁。Socket就是該模式的一個(gè)實(shí)現(xiàn)随橘, socket即是一種特殊的文件,一些socket函數(shù)就是對(duì)其進(jìn)行的操作(讀/寫IO锦庸、打開(kāi)机蔗、關(guān)閉).
說(shuō)白了Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層,它是一組接口甘萧。在設(shè)計(jì)模式中萝嘁,Socket其實(shí)就是一個(gè)門面模式,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面扬卷,對(duì)用戶來(lái)說(shuō)牙言,一組簡(jiǎn)單的接口就是全部,讓Socket去組織數(shù)據(jù)邀泉,以符合指定的協(xié)議嬉挡。
注意:其實(shí)socket也沒(méi)有層的概念,它只是一個(gè)facade設(shè)計(jì)模式的應(yīng)用汇恤,讓編程變的更簡(jiǎn)單庞钢。是一個(gè)軟件抽象層。在網(wǎng)絡(luò)編程中因谎,我們大量用的都是通過(guò)socket實(shí)現(xiàn)的基括。
2.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ù)表示的,對(duì)應(yīng)的FILE *結(jié)構(gòu)的表示就是stdin遏暴、stdout侄刽、stderr
套接字API最初是作為UNIX操作系統(tǒng)的一部分而開(kāi)發(fā)的,所以套接字API與系統(tǒng)的其他I/O設(shè)備集成在一起朋凉。特別是州丹,當(dāng)應(yīng)用程序要為因特網(wǎng)通信而創(chuàng)建一個(gè)套接字(socket)時(shí),操作系統(tǒng)就返回一個(gè)小整數(shù)作為描述符(descriptor)來(lái)標(biāo)識(shí)這個(gè)套接字杂彭。然后墓毒,應(yīng)用程序以該描述符作為傳遞參數(shù),通過(guò)調(diào)用函數(shù)來(lái)完成某種操作(例如通過(guò)網(wǎng)絡(luò)傳送數(shù)據(jù)或接收輸入的數(shù)據(jù))亲怠。
在許多操作系統(tǒng)中蚁鳖,套接字描述符和其他I/O描述符是集成在一起的,所以應(yīng)用程序可以對(duì)文件進(jìn)行套接字I/O或I/O讀/寫操作赁炎。
當(dāng)應(yīng)用程序要?jiǎng)?chuàng)建一個(gè)套接字時(shí),操作系統(tǒng)就返回一個(gè)小整數(shù)作為描述符钾腺,應(yīng)用程序則使用這個(gè)描述符來(lái)引用該套接字需要I/O請(qǐng)求的應(yīng)用程序請(qǐng)求操作系統(tǒng)打開(kāi)一個(gè)文件徙垫。操作系統(tǒng)就創(chuàng)建一個(gè)文件描述符提供給應(yīng)用程序訪問(wèn)文件。從應(yīng)用程序的角度看放棒,文件描述符是一個(gè)整數(shù)姻报,應(yīng)用程序可以用它來(lái)讀寫文件。下圖顯示间螟,操作系統(tǒng)如何把文件描述符實(shí)現(xiàn)為一個(gè)指針數(shù)組吴旋,這些指針指向內(nèi)部數(shù)據(jù)結(jié)構(gòu)。
對(duì)于每個(gè)程序系統(tǒng)都有一張單獨(dú)的表厢破。精確地講荣瑟,系統(tǒng)為每個(gè)運(yùn)行的進(jìn)程維護(hù)一張單獨(dú)的文件描述符表。當(dāng)進(jìn)程打開(kāi)一個(gè)文件時(shí)摩泪,系統(tǒng)把一個(gè)指向此文件內(nèi)部數(shù)據(jù)結(jié)構(gòu)的指針寫入文件描述符表笆焰,并把該表的索引值返回給調(diào)用者 。應(yīng)用程序只需記住這個(gè)描述符见坑,并在以后操作該文件時(shí)使用它嚷掠。操作系統(tǒng)把該描述符作為索引訪問(wèn)進(jìn)程描述符表,通過(guò)指針找到保存該文件所有的信息的數(shù)據(jù)結(jié)構(gòu)荞驴。
針對(duì)套接字的系統(tǒng)數(shù)據(jù)結(jié)構(gòu):
1)不皆、套接字API里有個(gè)函數(shù)socket,它就是用來(lái)創(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ù)來(lái)指定具體細(xì)節(jié)悲雳。例如調(diào)用socket將創(chuàng)建一個(gè)新的描述符條目:
2)、雖然套接字的內(nèi)部數(shù)據(jù)結(jié)構(gòu)包含很多字段香追,但是系統(tǒng)創(chuàng)建套接字后合瓢,大多數(shù)字字段沒(méi)有填寫。應(yīng)用程序創(chuàng)建套接字后在該套接字可以使用之前透典,必須調(diào)用其他的過(guò)程來(lái)填充這些字段晴楔。
3、基本的socket接口函數(shù)
服務(wù)器端先初始化/創(chuàng)建Socket峭咒,然后與端口綁定/綁定地址(bind)税弃,對(duì)端口進(jìn)行監(jiān)聽(tīng)(listen),調(diào)用accept阻塞/等待連續(xù)凑队,等待客戶端連接则果。在這時(shí)如果有個(gè)客戶端初始化一個(gè)Socket,然后連接服務(wù)器(connect)漩氨,如果連接成功西壮,這時(shí)客戶端與服務(wù)器端的連接就建立了〗芯客戶端發(fā)送數(shù)據(jù)請(qǐng)求款青,服務(wù)器端接收請(qǐng)求并處理請(qǐng)求,然后把回應(yīng)數(shù)據(jù)發(fā)送給客戶端霍狰,客戶端讀取數(shù)據(jù)抡草,最后關(guān)閉連接,一次交互結(jié)束蔗坯。
3.1康震、socket函數(shù)
函數(shù)原型:
int socket(int protofamily, int type, int protocol);
返回值:
//返回sockfd sockfd是描述符,類似于open函數(shù)。
函數(shù)功能:
socket函數(shù)對(duì)應(yīng)于普通文件的打開(kāi)操作宾濒。普通文件的打開(kāi)操作返回一個(gè)文件描述字签杈,而socket()用于創(chuàng)建一個(gè)socket描述符(socket descriptor),它唯一標(biāo)識(shí)一個(gè)socket鼎兽。這個(gè)socket描述字跟文件描述字一樣答姥,后續(xù)的操作都有用到它,把它作為參數(shù)谚咬,通過(guò)它來(lái)進(jìn)行一些讀寫操作鹦付。
函數(shù)參數(shù):
protofamily:即協(xié)議域,又稱為協(xié)議族(family)择卦。常用的協(xié)議族有敲长,AF_INET(IPV4)郎嫁、AF_INET6(IPV6)、AF_LOCAL(或稱AF_UNIX祈噪,Unix域socket)泽铛、AF_ROUTE等等。協(xié)議族決定了socket的地址類型辑鲤,在通信中必須采用對(duì)應(yīng)的地址盔腔,如AF_INET決定了要用ipv4地址(32位的)與端口號(hào)(16位的)的組合、AF_UNIX決定了要用一個(gè)絕對(duì)路徑名作為地址月褥。
3.2弛随、bind()函數(shù)
函數(shù)功能:
bind()函數(shù)把一個(gè)地址族中的特定地址賦給socket,也可以說(shuō)是綁定ip端口和socket宁赤。例如對(duì)應(yīng)AF_INET舀透、AF_INET6就是把一個(gè)ipv4或ipv6地址和端口號(hào)組合賦給socket。
函數(shù)原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函數(shù)參數(shù):
- 函數(shù)的三個(gè)參數(shù)分別為:sockfd:即socket描述字决左,它是通過(guò)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é)議族的不同而不同挚躯,
- addrlen:對(duì)應(yīng)的是地址的長(zhǎng)度。
3.3擦秽、listen()码荔、connect()函數(shù)
函數(shù)功能:
如果作為一個(gè)服務(wù)器,在調(diào)用socket()感挥、bind()之后就會(huì)調(diào)用listen()來(lái)監(jiān)聽(tīng)這個(gè)socket缩搅,如果客戶端這時(shí)調(diào)用connect()發(fā)出連接請(qǐng)求,服務(wù)器端就會(huì)接收到這個(gè)請(qǐng)求触幼。
函數(shù)原型:
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函數(shù)參數(shù):
listen函數(shù)的第一個(gè)參數(shù)即為要監(jiān)聽(tīng)的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)類型的堂鲤,等待客戶的連接請(qǐng)求。
connect函數(shù)的第一個(gè)參數(shù)即為客戶端的socket描述字媒峡,第二參數(shù)為服務(wù)器的socket地址瘟栖,第三個(gè)參數(shù)為socket地址的長(zhǎng)度×掳ⅲ客戶端通過(guò)調(diào)用connect函數(shù)來(lái)建立與TCP服務(wù)器的連接半哟。成功返回0酬滤,若連接失敗則返回-1。
3.4寓涨、accept()函數(shù)
函數(shù)功能:
TCP服務(wù)器端依次調(diào)用socket()盯串、bind()、listen()之后戒良,就會(huì)監(jiān)聽(tīng)指定的socket地址了体捏。TCP客戶端依次調(diào)用socket()、connect()之后就向TCP服務(wù)器發(fā)送了一個(gè)連接請(qǐng)求蔬墩。TCP服務(wù)器監(jiān)聽(tīng)到這個(gè)請(qǐng)求之后译打,就會(huì)調(diào)用accept()函數(shù)取接收請(qǐng)求,這樣連接就建立好了拇颅。之后就可以開(kāi)始網(wǎng)絡(luò)I/O操作了奏司,即類同于普通文件的讀寫I/O操作。
函數(shù)原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回連接connect_fd
函數(shù)參數(shù):
sockfd:
參數(shù)sockfd就是上面解釋中的監(jiān)聽(tīng)套接字樟插,這個(gè)套接字用來(lái)監(jiān)聽(tīng)一個(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)鸵熟。
addr:
這是一個(gè)結(jié)果參數(shù)副编,它用來(lái)接受一個(gè)返回值,這返回值指定客戶端的地址流强,當(dāng)然這個(gè)地址是通過(guò)某個(gè)地址結(jié)構(gòu)來(lái)描述的痹届,用戶應(yīng)該知道這一個(gè)什么樣的地址結(jié)構(gòu)。如果對(duì)客戶的地址不感興趣打月,那么可以把這個(gè)值設(shè)置為NULL队腐。
len:
如同大家所認(rèn)為的,它也是結(jié)果的參數(shù)奏篙,用來(lái)接受上述addr的結(jié)構(gòu)的大小的柴淘,它指明addr結(jié)構(gòu)所占有的字節(jié)個(gè)數(shù)。同樣的秘通,它也可以被設(shè)置為NULL为严。
如果accept成功返回,則服務(wù)器與客戶已經(jīng)正確建立連接了肺稀,此時(shí)服務(wù)器通過(guò)accept返回的套接字來(lái)完成與客戶的通信梗脾。
注意:
accept默認(rèn)會(huì)阻塞進(jìn)程,直到有一個(gè)客戶連接建立后返回盹靴,它返回的是一個(gè)新可用的套接字炸茧,這個(gè)套接字是連接套接字瑞妇。
此時(shí)我們需要區(qū)分兩種套接字:
監(jiān)聽(tīng)套接字: 監(jiān)聽(tīng)套接字正如accept的參數(shù)sockfd,它是監(jiān)聽(tīng)套接字梭冠,在調(diào)用listen函數(shù)之后辕狰,是服務(wù)器開(kāi)始調(diào)用socket()函數(shù)生成的,稱為監(jiān)聽(tīng)socket描述字(監(jiān)聽(tīng)套接字)
連接套接字:一個(gè)套接字會(huì)從主動(dòng)連接的套接字變身為一個(gè)監(jiān)聽(tīng)套接字控漠;而accept函數(shù)返回的是已連接socket描述字(一個(gè)連接套接字)蔓倍,它代表著一個(gè)網(wǎng)絡(luò)已經(jīng)存在的點(diǎn)點(diǎn)連接。
一個(gè)服務(wù)器通常通常僅僅只創(chuàng)建一個(gè)監(jiān)聽(tīng)socket描述字盐捷,它在該服務(wù)器的生命周期內(nèi)一直存在偶翅。內(nèi)核為每個(gè)由服務(wù)器進(jìn)程接受的客戶連接創(chuàng)建了一個(gè)已連接socket描述字,當(dāng)服務(wù)器完成了對(duì)某個(gè)客戶的服務(wù)碉渡,相應(yīng)的已連接socket描述字就被關(guān)閉聚谁。
連接套接字socketfd_new 并沒(méi)有占用新的端口與客戶端通信牍帚,依然使用的是與監(jiān)聽(tīng)套接字socketfd一樣的端口號(hào)
3.5瓣距、recv()/send()函數(shù)
當(dāng)然也可以使用其他函數(shù)來(lái)實(shí)現(xiàn)數(shù)據(jù)傳送司草,比如read和write把兔。
3.5.1 send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
不論是客戶還是服務(wù)器應(yīng)用程序都用send函數(shù)來(lái)向TCP連接的另一端發(fā)送數(shù)據(jù)。
客戶程序一般用send函數(shù)向服務(wù)器發(fā)送請(qǐng)求育瓜,而服務(wù)器則通常用send函數(shù)來(lái)向客戶程序發(fā)送應(yīng)答似袁。
第一個(gè)參數(shù)指定發(fā)送端套接字描述符存璃;
第二個(gè)參數(shù)指明一個(gè)存放應(yīng)用程序要發(fā)送數(shù)據(jù)的緩沖區(qū)淋叶;
第三個(gè)參數(shù)指明實(shí)際要發(fā)送的數(shù)據(jù)的字節(jié)數(shù)阎曹;
第四個(gè)參數(shù)一般置0。
3.5.2 recv
int recv( SOCKET s, char FAR *buf, int len, int flags );
不論是客戶還是服務(wù)器應(yīng)用程序都用recv函數(shù)從TCP連接的另一端接收數(shù)據(jù)煞檩。
該函數(shù)的第一個(gè)參數(shù)指定接收端套接字描述符处嫌;
第二個(gè)參數(shù)指明一個(gè)緩沖區(qū),該緩沖區(qū)用來(lái)存放recv函數(shù)接收到的數(shù)據(jù)形娇;
第三個(gè)參數(shù)指明buf的長(zhǎng)度;
第四個(gè)參數(shù)一般置0筹误。
3.6桐早、close()函數(shù)
函數(shù)功能:
在服務(wù)器與客戶端建立連接之后,會(huì)進(jìn)行一些讀寫操作厨剪,完成了讀寫操作就要關(guān)閉相應(yīng)的socket描述字哄酝,好比操作完打開(kāi)的文件要調(diào)用fclose關(guān)閉打開(kāi)的文件。
函數(shù)原型:
#include <unistd.h>
int close(int fd);
close一個(gè)TCP socket的缺省行為時(shí)把該socket標(biāo)記為以關(guān)閉祷膳,然后立即返回到調(diào)用進(jìn)程陶衅。該描述字不能再由調(diào)用進(jìn)程使用,也就是說(shuō)不能再作為read或write的第一個(gè)參數(shù)直晨。
注意:
close操作只是使相應(yīng)socket描述字的引用計(jì)數(shù)-1搀军,只有當(dāng)引用計(jì)數(shù)為0的時(shí)候膨俐,才會(huì)觸發(fā)TCP客戶端向服務(wù)器發(fā)送終止連接請(qǐng)求。
4罩句、Linux下Socket編程實(shí)例
我們?cè)赬shell5中焚刺,開(kāi)啟兩個(gè)會(huì)話,分別用來(lái)運(yùn)行socket_server端门烂、socket_client端乳愉。
4.1 編寫socket_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//第一步:導(dǎo)入Socket編程的標(biāo)準(zhǔn)庫(kù)
//這個(gè)標(biāo)準(zhǔn)庫(kù):linux數(shù)據(jù)類型(size_t、time_t等等)
#include <sys/types.h>
//提供socket函數(shù)以及數(shù)據(jù)結(jié)構(gòu)
#include <sys/socket.h>
//數(shù)據(jù)解構(gòu)(sockaddr_in)
#include <netinet/in.h>
//IP地址的轉(zhuǎn)換函數(shù)
#include <arpa/inet.h>
//定義服務(wù)端
#define SERVER_PORT 9999
int main(){
//第二步:創(chuàng)建socket
//服務(wù)端的socket
int server_socket_fd;
//客戶端
int client_socket_fd;
//服務(wù)端網(wǎng)絡(luò)地址
struct sockaddr_in server_addr;
//客戶端網(wǎng)絡(luò)地址
struct sockaddr_in client_addr;
//初始化網(wǎng)絡(luò)地址
//參數(shù)一:傳變量的地址($server_addr)
//參數(shù)二:開(kāi)始為止
//參數(shù)三:大小
//初始化服務(wù)端網(wǎng)絡(luò)地址
memset(&server_addr,0,sizeof(server_addr ));
//初始化客戶端網(wǎng)絡(luò)地址
//memset(&client_addr,0,sizeof(client_addr));
//設(shè)置服務(wù)端網(wǎng)絡(luò)地址-協(xié)議簇(sin_family)
//AF_INET:TCP/IP協(xié)議屯远、UDP
//AF_ISO:ISO 協(xié)議
server_addr.sin_family = AF_INET;
//設(shè)置服務(wù)端IP地址(自動(dòng)獲取系統(tǒng)默認(rèn)的本機(jī)IP蔓姚,自動(dòng)分配)
server_addr.sin_addr.s_addr = INADDR_ANY;
//設(shè)置服務(wù)端端口
server_addr.sin_port = htons(SERVER_PORT);
//創(chuàng)建服務(wù)端socket
//參數(shù)一(family):通信域(例如:IPV4->PF_INET、IPV6等等......)
//參數(shù)二(type):通信類型(例如:TCP->SOCK_STREAM,UDP->SOCK_DGRAM等等......)
//參數(shù)三(protocol):指定使用的協(xié)議(一般情況下都是默認(rèn)為0)
//默認(rèn)為0就是使用系統(tǒng)默認(rèn)的協(xié)議慨丐,系統(tǒng)支持什么我就就用什么
//TCP->IPPROTO_TCP
//UDP->IPPROTO_UDP
//SCTP->IPPROTO_SCTP
server_socket_fd = socket(PF_INET,SOCK_STREAM,0);
//判斷是否創(chuàng)建成功
if(server_socket_fd <0){
printf("create error!");
return 1;
}
printf("服務(wù)器創(chuàng)建成功!\n");
//服務(wù)端綁定地址
//參數(shù)一:服務(wù)端socket
//參數(shù)二:網(wǎng)絡(luò)地址
//參數(shù)三:數(shù)據(jù)類型大小
//socketaddr和sockaddr_in
bind(server_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
//監(jiān)聽(tīng)客戶端連接請(qǐng)求(服務(wù)端監(jiān)聽(tīng)有沒(méi)有客戶端連接)
//參數(shù)一:監(jiān)聽(tīng)的服務(wù)器socket
//參數(shù)二:客戶端數(shù)量(未處理隊(duì)列數(shù)量)
listen(server_socket_fd,6);
//接收客戶端連接
//參數(shù)一(sockfd):服務(wù)端
//參數(shù)二(addr):客戶端
//參數(shù)三(addrlen):大小
socklen_t sin_size = sizeof(struct sockaddr_in);
//獲取一個(gè)客戶端
client_socket_fd= accept(server_socket_fd,(struct sockaddr*)&client_socket_fd,&sin_size);
//判斷客戶端是否連接成功
if(client_socket_fd < 0){
printf("連接失敗");
return 1;
}
//連接成功:讀取客戶端數(shù)據(jù)
//BUFSIZ:默認(rèn)值
char buffer[BUFSIZ];
int len=0;
while(1){
//參數(shù)一:讀取客戶端數(shù)據(jù)(數(shù)據(jù)源)
//參數(shù)二:讀取到哪里(我們要讀取到緩沖區(qū)buffer)
//參數(shù)三:每次讀取多大BUFSIZ
//參數(shù)四:從哪里開(kāi)始讀0
len = recv(client_socket_fd,buffer,BUFSIZ,0);
if(len > 0){
//說(shuō)明讀取到了數(shù)據(jù)
printf("%s\n",buffer);
}
}
//關(guān)閉服務(wù)端和客戶端Socket
//參數(shù)一:關(guān)閉的源
//參數(shù)二:關(guān)閉的類型(設(shè)置權(quán)限)
//SHUT_RD:關(guān)閉讀(只允許寫坡脐,不允許讀)
//SHUT_WR:關(guān)閉寫(只允許讀,不允許寫)
//SHUT_RDWR:讀寫都關(guān)閉(書寫都不允許)
shutdown(client_socket_fd,SHUT_RDWR);
shutdown(server_socket_fd,SHUT_RDWR);
printf("server end.....\n");
getchar();
return 0;
}
4.2 執(zhí)行socket_server.c
root@jdu4e00u53f7:/usr/kpioneer/pthread# gcc -c socket_server.c
root@jdu4e00u53f7:/usr/kpioneer/pthread# gcc -o socket_server socket_server.o
root@jdu4e00u53f7:/usr/kpioneer/pthread# ./socket_server
服務(wù)器創(chuàng)建成功!
4.3 編寫socket_client.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//第一步:導(dǎo)入Socket編程的標(biāo)準(zhǔn)庫(kù)
//這個(gè)標(biāo)準(zhǔn)庫(kù):linux數(shù)據(jù)類型(size_t咖气、time_t等等......)
#include<sys/types.h>
//提供socket函數(shù)及數(shù)據(jù)結(jié)構(gòu)
#include<sys/socket.h>
//數(shù)據(jù)結(jié)構(gòu)(sockaddr_in)
#include<netinet/in.h>
//ip地址的轉(zhuǎn)換函數(shù)
#include<arpa/inet.h>
//定義服務(wù)器的端口號(hào)
#define SERVER_PORT 9999
int main(){
//客戶端socket
int client_socket_fd;
//服務(wù)端網(wǎng)絡(luò)地址
struct sockaddr_in server_addr;
//客戶端網(wǎng)絡(luò)地址
struct sockaddr_in client_addr;
//初始化網(wǎng)絡(luò)地址
//參數(shù)一:傳變量的地址($server_addr)
//參數(shù)二:開(kāi)始位置
//參數(shù)三:大小
//初始化服務(wù)端網(wǎng)絡(luò)地址
memset(&server_addr,0,sizeof(server_addr ));
//AF_INET:TCP/IP協(xié)議挨措、UDP
//AF_ISO:ISO 協(xié)議
server_addr.sin_family = AF_INET;
//設(shè)置服務(wù)端IP地址(自動(dòng)獲取系統(tǒng)默認(rèn)的本機(jī)IP,自動(dòng)分配)
server_addr.sin_addr.s_addr = INADDR_ANY;
//設(shè)置服務(wù)端端口
server_addr.sin_port = htons(SERVER_PORT);
//創(chuàng)建客戶端
client_socket_fd = socket(PF_INET,SOCK_STREAM,0);
//判斷是否創(chuàng)建成功
if(client_socket_fd < 0){
printf("create error!!!");
return 1;
}
//連接服務(wù)器
//參數(shù)一:哪一個(gè)客戶端
//參數(shù)二:連接服務(wù)器地址
//參數(shù)三:地址大小
int con_result = connect(client_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(con_result<0){
printf("connect error!");
return -1;
}
printf("create Socket Client\n ");
//發(fā)送消息(向服務(wù)器發(fā)送內(nèi)容)
char buffer[BUFSIZ] = "Hello, Socket Server!";
//參數(shù)一:指定客戶端
//參數(shù)二:指定緩沖區(qū)(沖那里數(shù)據(jù)讀取)
//參數(shù)三:實(shí)際讀取的大小strlen(buffer)(其實(shí)讀取到"\0"結(jié)束)
//參數(shù)四:從哪里開(kāi)始讀取
send(client_socket_fd,buffer,strlen(buffer),0);
//關(guān)閉
shutdown(client_socket_fd,SHUT_RDWR);
printf("client--- end-----\n");
return 0;
}
4.4 執(zhí)行socket_client.c
root@jdu4e00u53f7:/usr/kpioneer/pthread# gcc -o socket_client socket_client.o
root@jdu4e00u53f7:/usr/kpioneer/pthread# ./socket_client
create Socket Client
client--- end-----
4.5 再次查看socket_server
我們看到
服務(wù)器創(chuàng)建成功!
下多了一個(gè)打印語(yǔ)句Hello, Socket Server!
崩溪,程序運(yùn)行成功浅役。
5、Android下Socket編程實(shí)例(jni實(shí)現(xiàn))
我們新建一個(gè)Java工程和一個(gè)Android工程伶唯,分別用來(lái)運(yùn)行socket_server端觉既、socket_client端。
5.1 Java工程端
5.1.1 編寫 SocketServer.java
package com.haocai;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
public static void main(String[] args) {
try{
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("192.168.90.221",9998));
System.out.println("服務(wù)器Start...");
while(true){
//獲取連接客戶端
Socket socket = serverSocket.accept();
//讀取內(nèi)容
new ReaderThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
static class ReaderThread extends Thread{
BufferedReader bufferedReader;
public ReaderThread(Socket socket){
try {
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
super.run();
//循環(huán)讀取內(nèi)容
String content = null;
while(true){
try {
while((content = bufferedReader.readLine())!=null){
System.out.println("接收到了客戶端:"+content);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
5.1.2 運(yùn)行SocketServer
服務(wù)器Start...
5.2 Android工程端
5.2.1 編寫Java jni聲明
package com.haocai.socketclient;
public class SocketUtil {
public native void startClient(String serverIp,int serverPort);
static {
System.loadLibrary("socketlib");
}
}
5.2.2 編寫socket_client.c
#include"com_haocai_socketclient_SocketUtil.h"
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//第一步:導(dǎo)入Socket編程的標(biāo)準(zhǔn)庫(kù)
//這個(gè)標(biāo)準(zhǔn)庫(kù):linux數(shù)據(jù)類型(size_t乳幸、time_t等等......)
#include<sys/types.h>
//提供socket函數(shù)及數(shù)據(jù)結(jié)構(gòu)
#include<sys/socket.h>
//數(shù)據(jù)結(jié)構(gòu)(sockaddr_in)
#include<netinet/in.h>
//ip地址的轉(zhuǎn)換函數(shù)
#include<arpa/inet.h>
#include <android/log.h>
#define LOG_TAG "socket_client"
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,FORMAT,##__VA_ARGS__);
#define LOGD(FORMAT,...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,FORMAT, ##__VA_ARGS__);
JNIEXPORT void JNICALL Java_com_haocai_socketclient_SocketUtil_startClient
(JNIEnv *env, jobject jobj, jstring server_ip_jstr, jint server_port){
const char* server_ip = (*env)->GetStringUTFChars(env, server_ip_jstr, NULL);
//客戶端socket
int client_socket_fd;
//服務(wù)端網(wǎng)絡(luò)地址
struct sockaddr_in server_addr;
//初始化網(wǎng)絡(luò)地址
//參數(shù)一:傳變量的地址($server_addr)
//參數(shù)二:開(kāi)始位置
//參數(shù)三:大小
//初始化服務(wù)端網(wǎng)絡(luò)地址
memset(&server_addr,0,sizeof(server_addr));
//AF_INET:TCP/IP協(xié)議瞪讼、UDP
//AF_ISO:ISO 協(xié)議
server_addr.sin_family = AF_INET;
//設(shè)置服務(wù)端IP地址(自動(dòng)獲取系統(tǒng)默認(rèn)的本機(jī)IP,自動(dòng)分配)
server_addr.sin_addr.s_addr = inet_addr(server_ip);
//設(shè)置服務(wù)端端口
server_addr.sin_port = htons(server_port);
//創(chuàng)建客戶端
client_socket_fd = socket(PF_INET,SOCK_STREAM,0);
//判斷是否創(chuàng)建成功
if(client_socket_fd < 0){
LOGE("create error!");
return ;
}
//連接服務(wù)器
//參數(shù)一:哪一個(gè)客戶端
//參數(shù)二:連接服務(wù)器地址
//參數(shù)三:地址大小
int con_result = connect(client_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(con_result<0){
LOGE("connect error!");
return ;
}
//發(fā)送消息(向服務(wù)器發(fā)送內(nèi)容)
char buffer[BUFSIZ] = "Hello Socket Server!";
//參數(shù)一:指定客戶端
//參數(shù)二:指定緩沖區(qū)(沖那里數(shù)據(jù)讀取)
//參數(shù)三:實(shí)際讀取的大小strlen(buffer)(其實(shí)讀取到"\0"結(jié)束)
//參數(shù)四:從哪里開(kāi)始讀取
send(client_socket_fd,buffer,strlen(buffer),0);
//關(guān)閉
shutdown(client_socket_fd,SHUT_RDWR);
LOGI("client--- end-----");
(*env)->ReleaseStringUTFChars(env, server_ip_jstr, server_ip);
return ;
}
5.2.3 調(diào)用主程序MainActivity
package com.haocai.socketclient;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
public static final String SERVER_IP = "192.168.90.221";
public static final int SERVER_PORT = 9998;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startSocket(View v){
new Thread(new Runnable() {
@Override
public void run() {
SocketUtil socketUtil = new SocketUtil();
socketUtil.startClient(SERVER_IP,SERVER_PORT);
}
}).start();
}
}
5.3 再次查看Java工程Log
接收到了客戶端:Hello Socket Server!