Android NDK開(kāi)發(fā)之旅39--Linux&Android平臺(tái)下Socket編程

Android NDK開(kāi)發(fā)之旅 目錄

前言

我們做前端開(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ù):

  1. 函數(shù)的三個(gè)參數(shù)分別為:sockfd:即socket描述字决左,它是通過(guò)socket()函數(shù)創(chuàng)建了愕够,唯一標(biāo)識(shí)一個(gè)socket。bind()函數(shù)就是將給這個(gè)描述字綁定一個(gè)名字佛猛。
  2. addr:一個(gè)const struct sockaddr *指針链烈,指向要綁定給sockfd的協(xié)議地址。這個(gè)地址結(jié)構(gòu)根據(jù)地址創(chuàng)建socket時(shí)的地址協(xié)議族的不同而不同挚躯,
  3. 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!
源碼下載
Github:https://github.com/kpioneer123/SocketClient
特別感謝:

guisu--Linux的SOCKET編程詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末粹断,一起剝皮案震驚了整個(gè)濱河市符欠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓶埋,老刑警劉巖希柿,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異养筒,居然都是意外死亡曾撤,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門晕粪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)挤悉,“玉大人,你說(shuō)我怎么就攤上這事巫湘∽氨” “怎么了昏鹃?”我有些...
    開(kāi)封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)衅斩。 經(jīng)常有香客問(wèn)我盆顾,道長(zhǎng),這世上最難降的妖魔是什么畏梆? 我笑而不...
    開(kāi)封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任您宪,我火速辦了婚禮,結(jié)果婚禮上奠涌,老公的妹妹穿的比我還像新娘宪巨。我一直安慰自己,他們只是感情好溜畅,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布捏卓。 她就那樣靜靜地躺著,像睡著了一般慈格。 火紅的嫁衣襯著肌膚如雪怠晴。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天浴捆,我揣著相機(jī)與錄音蒜田,去河邊找鬼。 笑死选泻,一個(gè)胖子當(dāng)著我的面吹牛冲粤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播页眯,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼梯捕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了窝撵?” 一聲冷哼從身側(cè)響起傀顾,我...
    開(kāi)封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碌奉,沒(méi)想到半個(gè)月后短曾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡道批,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年错英,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了入撒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隆豹。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖茅逮,靈堂內(nèi)的尸體忽然破棺而出璃赡,到底是詐尸還是另有隱情判哥,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布碉考,位于F島的核電站塌计,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏侯谁。R本人自食惡果不足惜锌仅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望墙贱。 院中可真熱鬧热芹,春花似錦、人聲如沸惨撇。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)魁衙。三九已至报腔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剖淀,已是汗流浹背纯蛾。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留祷蝌,地道東北人茅撞。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像巨朦,于是被迫代替她去往敵國(guó)和親米丘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345