LInux下socket编程学习笔记,NDK开发之旅37

Android NDK开发之旅 目录

1.socket套接字:

前言

大家做前端开发主要用http/https请求,那种请求从数据更新角度是单向的,即用户发起呼吁才能取拿到新型数据。但偶尔,一些景象和数量的变更要立时推送到前端。例如O2O行业,消费者下定单
-> O2O商厦接受到订单 -> 送外卖小哥即时收到订单->
消费者实时收到外卖小哥和友好的距离。
里头,后两步要即时接到音讯,就得利用
Socket编程保持长连接。再比如说,音讯推送,语音聊天等。

注意:
HTTP也可以创立长连接的,使用Connection:keep-alive,HTTP
1.1暗中同意举办持之以恒连接。HTTP1.1和HTTP1.0相比较而言,最大的界别就是增多了持久连接扶助(貌似新颖的
http1.0 可以显得的指定
keep-alive),但还是无状态的,或许说是不得以信任的。

  socket起点于Unix,而Unix/Linux基本文学之一就是“一切皆文件”,都得以用“打开open
–> 读写write/read –>
关闭close”情势来操作。Socket就是该情势的七个完结,socket即是一种分外的文书,一些socket函数就是对其开展的操作(读/写IO、打开、关闭).
   
 说白了Socket是应用层与TCP/IP协议族通讯的中档软件抽象层,它是一组接口。在设计方式中,Socket其实就是二个伪装格局,它把纷纷的TCP/IP协议族隐藏在Socket接口前边,对用户来说,一组大致的接口就是全方位,让Socket去协会数量,以符合指定的情商。

一,互联网中经过之间怎么样通讯?

地方的长河间通讯(IPC)有很两种情势,但可以计算为上面4类:

  • 音信传递(管道、FIFO、音信队列)
  • 一路(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
  • 共享内存(匿名的和签署的)
  • 长途进程调用(Solaris门和Sun 纳瓦拉PC)

互联网中经过之间怎么通讯?
最首要化解的标题是怎么唯一标识3个历程,否则通信无从谈起!在本地可以通过进度PID来唯一标识一个进度,可是在互连网中那是行不通的。其实TCP/IP协议族已经帮我们缓解了这一个难点,网络层的“ip地址”可以唯一标识互连网中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进度)。这样利用雅士利组(ip地址,协议,端口)就足以标识网络的经过了,网络中的进度通信就可以运用那么些标志与任何进度展开互动。

利用TCP/IP协议的应用程序经常采用接纳编程接口:UNIX
BSD的套接字(socket)和UNIX System
V的TLI(已经被淘汰),来兑现网络进程之间的通讯。
就近日而言,大约全数的应用程序都以使用socket,最近日又是互连网时期,互联网中经过通讯是无处不在,那就是自个儿何以说“一切皆socket”。

       注意:其实socket也从未层的概念,它只是多个facade设计形式的应用,让编程变的更简短。是2个软件抽象层。在网络编程中,大家大量用的都以因而socket完成的。

2. Socket是什么

2、套接字描述符

 
其实就是四个整数,大家最熟识的句柄是0、一,2多少个,0是标准输入,1是专业输出,2是专业错误输出。0、一,2是整数表示的,对应的FILE
*协会的意味就是stdin、stdout、stderr

套接字API最初是作为UNIX操作系统的一部分而付出的,所以套接字API与系统的任何I/O设备集成在一道。尤其是,当应用程序要为因特网通讯而创办三个套接字(socket)时,操作系统就赶回2个小平头作为描述符(descriptor)来标识那几个套接字。然后,应用程序以该描述符作为传递参数,通过调用函数来成功某种操作(例如通过网络传送数据或收受输入的数目)。

在广大操作系统中,套接字描述符和此外I/O描述符是集成在一起的,所以应用程序可以对文件进行套接字I/O或I/O读/写操作。

当应用程序要开创1个套接字时,操作系统就重临二个小平头作为描述符,应用程序则应用这几个描述符来引用该套接字需要I/O请求的应用程序请求操作系统打开多个文件。操作系统就创立一个文书讲述符提必要应用程序访问文件。从应用程序的角度看,文件讲述符是一个平头,应用程序可以用它来读写文件。

2.1 socket套接字:

socket起点于Unix,而Unix/Linux基本历史学之一就是“一切皆文件”,都得以用“打开open
–> 读写write/read –>
关闭close”方式来操作。Socket就是该形式的叁个兑现,
socket即是一种卓殊的文书,一些socket函数就是对其开展的操作(读/写IO、打开、关闭).
粗略Socket是应用层与TCP/IP协议族通讯的高中级软件抽象层,它是一组接口。在设计情势中,Socket其实就是1个伪装方式,它把纷纭的TCP/IP协议族隐藏在Socket接口前面,对用户来说,一组大致的接口就是全方位,让Socket去协会数量,以契合指定的情商。

留神:其实socket也不曾层的定义,它只是八个facade设计格局的运用,让编程变的更简单。是1个软件抽象层。在互联网编程中,大家多量用的都以由此socket已毕的。

4.1、socket()函数

        int  socket(int protofamily, int type, int protocol);//返回sockfd

     sockfd是讲述符。

  socket函数对应于普通文书的开拓操作。普通文书的打开操作重回三个文本讲述字,而socket()用于创设二个socket描述符(socket
descriptor),它唯一标识一个socket。那个socket描述字跟文件讲述字一样,后续的操作都有应用它,把它看成参数,通过它来开展局地读写操作。

     
正如可以给fopen的散播不一样参数值,以开辟不一样的公文。创立socket的时候,也得以指定不一样的参数创造不一样的socket描述符,socket函数的多个参数分别为:

  • protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地点类型,在通讯中务必拔取对应的地点,如AF_INET决定了要用ipv4地址(三贰十一个人的)与端口号(十四个人的)的结合、AF_UNIX决定了要用七个纯属路径名作为地点。
  • type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的项目有何?)。
  • protocol:故名思意,就是点名协议。常用的商事有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(那些协议本人将会独自开篇商量!)。

注意:并不是下面的type和protocol可以任意组合的,如SOCK_STREAM不得以跟IPPROTO_UDP组合。当protocol为0时,会自动接纳type类型对应的暗中同意协议。

当大家调用socket创立一个socket时,再次来到的socket描述字它存在于协议族(address
family,AF_XXX)空间中,但绝非2个有血有肉的地址。假若想要给它赋值二个地点,就非得调用bind()函数,否则就当调用connect()、listen()时系统会活动随机分配多少个端口。

2.二,套接字描述符

实质上就是壹个整数,大家最熟稔的句柄是0、1、2多个,0是正规输入,1是正式输出,2是正式错误输出。0、一,2是整数表示的,对应的FILE
*布局的意味就是stdin、stdout、stderr

套接字API最初是用作UNIX操作系统的一局地而开发的,所以套接字API与系统的别的I/O设备集成在共同。特别是,当应用程序要为因特网通讯而创设二个套接字(socket)时,操作系统就回去二个小平头作为描述符(descriptor)来标识那几个套接字。然后,应用程序以该描述符作为传递参数,通过调用函数来形成某种操作(例如通过互连网传送数据或接收输入的数额)。

在不可胜道操作系统中,套接字描述符和其余I/O描述符是集成在协同的,所以应用程序可以对文件举行套接字I/O或I/O读/写操作。

当应用程序要创立一个套接字时,操作系统就回到2个小平头作为描述符,应用程序则采取那么些描述符来引用该套接字要求I/O请求的应用程序请求操作系统打开3个文本。操作系统就创办1个文件讲述符提需要应用程序访问文件。从应用程序的角度看,文件讲述符是1个整数,应用程序可以用它来读写文件。下图体现,操作系统怎么样把文件讲述符完成为二个指南针数组,这几个指针指向里面数据结构。

对此逐个程序系统都有一张单独的表。精确地讲,系统为每一种运营的历程维护一张单独的文本讲述符表。当进度打开一个文书时,系统把一个针对性此文件之中数据结构的指针写入文件讲述符表,并把该表的索引值再次回到给调用者
。应用程序只需记住那几个描述符,并在今后操作该公文时行使它。操作系统把该描述符作为目录访问进度描述符表,通过指针找到保存该公文全部的新闻的数据结构。

针对套接字的体系数据结构:

1)、套接字API里有个函数socket,它就是用来创立二个套接字。套接字设计的一体化思路是,单个系统调用就足以创立任何套接字,因为套接字是一定笼统的。一旦套接字成立后,应用程序还亟需调用其余函数来指定具体细节。例如调用socket将开创三个新的描述符条目:

2)、就算套接字的其中数据结构包蕴众多字段,不过系统创立套接字后,一大半字字段没有填写。应用程序创设套接字后在该套接字可以拔取之前,必须调用其余的经过来填充那些字段。

4.2、bind()函数

正如上面所说bind()函数把二个地址族中的特定地点赋给socket。例如对应AF_INET、AF_INET6就是把1个ipv4或ipv6地址和端口号组合赋给socket。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数的多个参数分别为:

  • sockfd:即socket描述字,它是由此socket()函数成立了,唯一标识3个socket。bind()函数就是将给这么些描述字绑定一个名字。
  • addr:一个const struct sockaddr
    *指南针,指向要绑定给sockfd的商事地址。那几个地方结构依据地点创立socket时的地点协议族的差距而分歧,如ipv4对应的是: 

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

    ipv6对应的是: 

    struct sockaddr_in6 { 
        sa_family_t     sin6_family;   /* AF_INET6 */ 
        in_port_t       sin6_port;     /* port number */ 
        uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
        struct in6_addr sin6_addr;     /* IPv6 address */ 
        uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
    };
    
    struct in6_addr { 
        unsigned char   s6_addr[16];   /* IPv6 address */ 
    };
    

    Unix域对应的是: 

    #define UNIX_PATH_MAX    108
    
    struct sockaddr_un { 
        sa_family_t sun_family;               /* AF_UNIX */ 
        char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
    };
    
  • addrlen:对应的是地点的长短。

一般说来服务器在起步的时候都会绑定2个显眼的地方(如ip地址+端口号),用于提供劳动,客户就足以由此它来三番五次服务器;而客户端就毫无指定,有系统自动分配三个端口号和本身的ip地址组合。这就是怎么常常服务器端在listen此前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

叁,基本的socket接口函数

 服务器端先初步化/创制Socket,然后与端口绑定/绑定地址(bind),对端口进行监听(listen),调用accept阻塞/等待一而再,等待客户端连接。在此刻尽管有个客户端开头化三个Socket,然后连接服务器(connect),借使总是成功,那时客户端与劳动器端的一连就建立了。客户端发送数据请求,服务器端接收请求并拍卖请求,然后把回应数据发送给客户端,客户端读取数据,最终关闭连接,一遍交互为止。

4.3、listen()、connect()函数

万一作为三个服务器,在调用socket()、bind()之后就会调用listen()来监听那么些socket,尽管客户端那时调用connect()发出连接请求,服务器端就会吸纳到那一个请求。

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

listen函数的第一个参数即为要监听的socket描述字,第四个参数为对应socket可以排队的最辛辛那提接个数。socket()函数创制的socket暗中同意是三个积极向上类型的,listen函数将socket变为被动类型的,等待客户的一而再请求。

connect函数的率先个参数即为客户端的socket描述字,第一参数为服务器的socket地址,第多个参数为socket地址的尺寸。客户端通过调用connect函数来树立与TCP服务器的连天。

3.1、socket函数

函数原型:

  int socket(int protofamily, int type, int protocol);

返回值:
  //再次来到sockfd sockfd是描述符,类似于open函数。

函数功效:

socket函数对应于普通文书的开辟操作。普通文书的开拓操作重回2个文书讲述字,而socket()用于创制三个socket描述符(socket
descriptor),它唯一标识3个socket。这些socket描述字跟文件讲述字一样,后续的操作都有拔取它,把它看做参数,通过它来进展部分读写操作。

函数参数:

protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地点类型,在通讯中务必使用对应的地方,如AF_INET决定了要用ipv4地址(33人的)与端口号(拾二人的)的结缘、AF_UNIX决定了要用多少个相对路径名作为地方。

4.4、accept()函数

TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了二个连接请求。TCP服务器监听到这一个请求之后,就会调用accept()函数取接收请求,这样总是就建立好了。之后就可以起来网络I/O操作了,即一般于常见文书的读写I/O操作。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回连接connect_fd 

参数sockfd
参数sockfd就是地点表达中的监听套接字,那一个套接字用来监听三个端口,当有二个客户与服务器连接时,它拔取那几个二个端口号,而那时候以此端口号正与那些套接字关联。当然客户不精通套接字那几个细节,它只通晓1个地点和多个端口号。

参数addr
那是二个结出参数,它用来经受3个再次来到值,那重临值指定客户端的地方,当然这一个地址是经过有个别地点结构来描述的,用户应该明了那贰个怎么样的地方结构。假如对客户的地方不感兴趣,那么可以把那一个值设置为NULL。

参数len
有如我们所认为的,它也是结果的参数,用来接受上述addr的布局的大大小小的,它指明addr结构所占有的字节个数。同样的,它也得以被装置为NULL。

 

比方accept成功重临,则服务器与客户已经正确树立连接了,此时服务器通过accept再次回到的套接字来成功与客户的通讯。

威尼斯人6799.com,注意:

     
accept暗许会阻塞进度,直到有二个客户连接建立后重返,它回到的是3个新可用的套接字,那些套接字是连接套接字。

此刻我们须要区分三种套接字,

       监听套接字:
监听套接字正如accept的参数sockfd,它是监听套接字,在调用listen函数之后,是服务器初始调用socket()函数生成的,称为监听socket描述字(监听套接字)

     
 连接套接字:3个套接字会从积极连接的套接字变身为二个监听套接字;而accept函数再次回到的是已三番五次socket描述字(七个连接套接字),它象征着二个互联网已经存在的点点连接。

       
1个服务器常常一般只是只创建二个监听socket描述字,它在该服务器的生命周期内直接留存。内核为每一种由服务器进程接受的客户连接创造了贰个已连接socket描述字,当服务器已毕了对有些客户的服务,相应的已一连socket描述字就被关门。

       
自然要问的是:为啥要有二种套接字?原因很粗略,假设采纳五个描述字的话,那么它的作用太多,使得应用很不直观,同时在基本确实发生了3个如此的新的讲述字。

连接套接字socketfd_new
并不曾据为己有新的端口与客户端通讯,依旧采纳的是与监听套接字socketfd一样的端口号

3.2、bind()函数

函数作用:
  bind()函数把贰个地址族中的特定地点赋给socket,也足以说是绑定ip端口和socket。例如对应AF_INET、AF_INET6就是把二个ipv4或ipv6地址和端口号组合赋给socket。

函数原型:

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数参数:

  1. 函数的八个参数分别为:sockfd:即socket描述字,它是经过socket()函数创制了,唯一标识三个socket。bind()函数就是将给这一个描述字绑定二个名字。
  2. addr:一个const struct sockaddr
    *指南针,指向要绑定给sockfd的商议地址。这几个地址结构依照地点创造socket时的地点协议族的两样而不一样,
  3. addrlen:对应的是地点的长短。

4.5、read()、write()等函数

万事具备只欠南风,至此服务器与客户已经建立好连接了。能够调用网络I/O举行读写操作了,即落成了网咯中不相同进度之间的通讯!互联网I/O操作有下边几组:

  • read()/write()
  • recv()/send()
  • readv()/writev()
  • recvmsg()/sendmsg()
  • recvfrom()/sendto()

自作者引进应用recvmsg()/sendmsg()函数,那多少个函数是最通用的I/O函数,实际上可以把地点的任何函数都替换来那五个函数。它们的宣示如下:

       #include <unistd.h>

       size_t read(int fd, void *buf, size_t count);
       size_t write(int fd, const void *buf, size_t count);

       #include <sys/types.h>
       #include <sys/socket.h>

       size_t send(int sockfd, const void *buf, size_t len, int flags);
       size_t recv(int sockfd, void *buf, size_t len, int flags);

       size_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
       size_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

       size_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
       size_t recvmsg(int sockfd, struct msghdr *msg, int flags);

read函数是背负从fd中读取内容.当读成功时,read重临实际所读的字节数,如若回去的值是0表示曾经读到文件的了断了,小于0代表出现了错误。如若不当为EINT大切诺基表明读是由刹车引起的,假如是ECONNREST表示互连网连接出了难题。

write函数将buf中的nbytes字节内容写入文件讲述符fd.成功时再次来到写的字节数。战败时回来-1,并设置errno变量。
在互联网程序中,当咱们向套接字文件讲述符写时有俩种只怕。1)write的再次回到值大于0,表示写了一部分依旧是一体的数目。2)重回的值小于0,此时面世了不当。大家要根据错误类型来拍卖。倘若不当为EINTCRUISER表示在写的时候出现了刹车错误。若是为EPIPE表示网络连接出现了难题(对方早已倒闭了连接)。

任何的自小编就不一一介绍这几对I/O函数了,具体参见man文档可能baidu、谷歌(Google),上面的例子少将使用到send/recv。

3.3、listen()、connect()函数

函数成效:
  假使作为八个服务器,在调用socket()、bind()之后就会调用listen()来监听那个socket,倘使客户端这时调用connect()发出连接请求,服务器端就会吸纳到这些请求。
函数原型:

  int listen(int sockfd, int backlog);
  int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数参数:
  listen函数的首先个参数即为要监听的socket描述字,首个参数为相应socket可以排队的最菲尼克斯接个数。socket()函数创设的socket暗中认同是壹个积极类型的,listen函数将socket变为被动类型的,等待客户的总是请求。
  connect函数的首先个参数即为客户端的socket描述字,第壹参数为服务器的socket地址,第四个参数为socket地址的长短。客户端通过调用connect函数来建立与TCP服务器的接连。成功再次回到0,若连续战败则赶回-1。

4.6、close()函数

在服务器与客户端建立连接之后,会进展部分读写操作,落成了读写操作就要关闭相应的socket描述字,好比操作完打开的公文要调用fclose关闭打开的文件。

#include <unistd.h>
int close(int fd);

close二个TCP
socket的缺省行为时把该socket标记为以关闭,然后随即回去到调用进度。该描述字无法再由调用进度使用,约等于说不能再作为read或write的率先个参数。

小心:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

威尼斯人6799.com 1

本篇文章引用<a
href=”http://blog.csdn.net/hguisu/article/details/7445768"&gt;[Linux的SOCKET编程详解](http://blog.csdn.net/hguisu/article/details/7445768)&lt;/a&gt;

3.4、accept()函数

函数功用:
  TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了3个老是请求。TCP服务器监听到那么些请求之后,就会调用accept()函数取接收请求,这样总是就建立好了。之后就可以起来网络I/O操作了,即一般于平时文书的读写I/O操作。
函数原型:

  int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回连接connect_fd

函数参数:
sockfd:
  参数sockfd就是下面表明中的监听套接字,这么些套接字用来监听贰个端口,当有1个客户与服务器连接时,它利用这么些1个端口号,而此刻以此端口号正与那么些套接字关联。当然客户不亮堂套接字那些细节,它只驾驭二个地点和2个端口号。
addr:
  那是1个结实参数,它用来接受1个再次回到值,那再次来到值指定客户端的地方,当然这几个地方是通过有些地点结构来叙述的,用户应该掌握那二个怎样的地址结构。若是对客户的地方不感兴趣,那么可以把那些值设置为NULL。
len:
  就像大家所认为的,它也是结果的参数,用来接受上述addr的构造的尺寸的,它指明addr结构所占据的字节个数。同样的,它也得以被设置为NULL。
只要accept成功再次来到,则服务器与客户已经正确树立连接了,此时服务器通过accept重回的套接字来成功与客户的通讯。

注意:

accept暗中认同会阻塞进度,直到有一个客户连接建立后回来,它回到的是二个新可用的套接字,这一个套接字是连接套接字。
那会儿我们必要区分两种套接字:
  监听套接字:
监听套接字正如accept的参数sockfd,它是监听套接字,在调用listen函数之后,是服务器初阶调用socket()函数生成的,称为监听socket描述字(监听套接字)
  连接套接字:贰个套接字会从积极连接的套接字变身为二个监听套接字;而accept函数再次回到的是已三番五次socket描述字(多个连接套接字),它表示着二个网络已经存在的点点连接。
三个服务器日常一般只是只创制多少个监听socket描述字,它在该服务器的生命周期内向来留存。内核为各个由服务器进度接受的客户连接创制了3个已两次三番socket描述字,当服务器完结了对某些客户的劳动,相应的已连接socket描述字就被关闭。
  连接套接字socketfd_new
并没有据为己有新的端口与客户端通讯,如故选拔的是与监听套接字socketfd一样的端口号

3.5、recv()/send()函数

理所当然也得以采纳任何函数来落到实处数据传送,比如read和write。

3.5.1 send
 ssize_t send(int sockfd, const void *buf, size_t len, int flags);

无论是客户照旧服务器应用程序都用send函数来向TCP连接的另一端发送数据。

客户程序一般用send函数向服务器发送请求,而服务器则一般用send函数来向客户程序发送应答。
  第1个参数指定发送端套接字描述符;
  首个参数指可瑞康个存放应用程序要发送数据的缓冲区;
  第一个参数指明实际要发送的数目标字节数;
  第柒,个参数一般置0。

3.5.2 recv
  int recv( SOCKET s,  char FAR *buf, int len, int flags );   

无论是是客户依然服务器应用程序都用recv函数从TCP连接的另一端接收数据。
 该函数的第2、个参数指定接收端套接字描述符;
 第2、个参数指爱他美个缓冲区,该缓冲区用来存放在recv函数接收到的多寡;
 第9个参数指明buf的长短;
 第7个参数一般置0。

3.6、close()函数

函数功效:
  在服务器与客户端建立连接之后,会开展局地读写操作,达成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文书。
函数原型:

#include <unistd.h>
int close(int fd);

close二个TCP
socket的缺省行为时把该socket标记为以关闭,然后霎时回到到调用进度。该描述字不或者再由调用进度使用,相当于说不恐怕再作为read或write的第多个参数。

注意:
close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

4、Linux下Socket编程实例

我们在Xshell5中,开启多个会话,分别用来运维socket_server端、socket_client端。
4.1 编写socket_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//第一步:导入Socket编程的标准库
//这个标准库:linux数据类型(size_t、time_t等等)

#include <sys/types.h>
//提供socket函数以及数据结构
#include <sys/socket.h>

//数据解构(sockaddr_in)
#include <netinet/in.h>
//IP地址的转换函数
#include <arpa/inet.h>

//定义服务端
#define SERVER_PORT 9999


int main(){

 //第二步:创建socket
 //服务端的socket
 int server_socket_fd;
 //客户端
 int client_socket_fd;
 //服务端网络地址
 struct sockaddr_in  server_addr;
 //客户端网络地址
 struct sockaddr_in client_addr;

 //初始化网络地址
 //参数一:传变量的地址($server_addr)
 //参数二:开始为止 
 //参数三:大小
 //初始化服务端网络地址
 memset(&server_addr,0,sizeof(server_addr ));
 //初始化客户端网络地址
 //memset(&client_addr,0,sizeof(client_addr));

 //设置服务端网络地址-协议簇(sin_family)
 //AF_INET:TCP/IP协议、UDP
  //AF_ISO:ISO 协议         
 server_addr.sin_family = AF_INET;

 //设置服务端IP地址(自动获取系统默认的本机IP,自动分配)
 server_addr.sin_addr.s_addr = INADDR_ANY;

 //设置服务端端口
 server_addr.sin_port = htons(SERVER_PORT);

 //创建服务端socket 
 //参数一(family):通信域(例如:IPV4->PF_INET、IPV6等等......)
 //参数二(type):通信类型(例如:TCP->SOCK_STREAM,UDP->SOCK_DGRAM等等......)
 //参数三(protocol):指定使用的协议(一般情况下都是默认为0)
 //默认为0就是使用系统默认的协议,系统支持什么我就就用什么
 //TCP->IPPROTO_TCP
 //UDP->IPPROTO_UDP
 //SCTP->IPPROTO_SCTP
  server_socket_fd = socket(PF_INET,SOCK_STREAM,0);

  //判断是否创建成功
  if(server_socket_fd <0){
     printf("create error!");
     return 1;
  }


  printf("服务器创建成功!\n");

  //服务端绑定地址
  //参数一:服务端socket
  //参数二:网络地址
  //参数三:数据类型大小
  //socketaddr和sockaddr_in
  bind(server_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));

  //监听客户端连接请求(服务端监听有没有客户端连接)
  //参数一:监听的服务器socket
  //参数二:客户端数量(未处理队列数量)
  listen(server_socket_fd,6);

  //接收客户端连接
  //参数一(sockfd):服务端
  //参数二(addr):客户端
  //参数三(addrlen):大小
 socklen_t sin_size = sizeof(struct sockaddr_in);
 //获取一个客户端
 client_socket_fd= accept(server_socket_fd,(struct sockaddr*)&client_socket_fd,&sin_size);
 //判断客户端是否连接成功
 if(client_socket_fd < 0){
     printf("连接失败");
     return 1;
 }
 //连接成功:读取客户端数据
 //BUFSIZ:默认值
 char buffer[BUFSIZ];
 int len=0;
 while(1){
     //参数一:读取客户端数据(数据源)
     //参数二:读取到哪里(我们要读取到缓冲区buffer)
     //参数三:每次读取多大BUFSIZ
     //参数四:从哪里开始读0

     len = recv(client_socket_fd,buffer,BUFSIZ,0);
     if(len > 0){
       //说明读取到了数据
       printf("%s\n",buffer);

     }
 }
 //关闭服务端和客户端Socket
 //参数一:关闭的源
 //参数二:关闭的类型(设置权限)
 //SHUT_RD:关闭读(只允许写,不允许读)
 //SHUT_WR:关闭写(只允许读,不允许写)
 //SHUT_RDWR:读写都关闭(书写都不允许)
 shutdown(client_socket_fd,SHUT_RDWR);
 shutdown(server_socket_fd,SHUT_RDWR);


 printf("server end.....\n");
 getchar();
 return 0;

}
4.2 执行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
服务器创建成功!
4.3 编写socket_client.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//第一步:导入Socket编程的标准库
//这个标准库:linux数据类型(size_t、time_t等等......)
#include<sys/types.h>
//提供socket函数及数据结构
#include<sys/socket.h>
//数据结构(sockaddr_in)
#include<netinet/in.h>
//ip地址的转换函数
#include<arpa/inet.h>

//定义服务器的端口号
#define SERVER_PORT 9999

int main(){

    //客户端socket
    int client_socket_fd;

    //服务端网络地址
    struct sockaddr_in  server_addr;
    //客户端网络地址
    struct sockaddr_in client_addr;

    //初始化网络地址
    //参数一:传变量的地址($server_addr)
    //参数二:开始位置 
    //参数三:大小
    //初始化服务端网络地址
     memset(&server_addr,0,sizeof(server_addr ));
    //AF_INET:TCP/IP协议、UDP
    //AF_ISO:ISO 协议         
     server_addr.sin_family = AF_INET;

    //设置服务端IP地址(自动获取系统默认的本机IP,自动分配)
    server_addr.sin_addr.s_addr = INADDR_ANY;

     //设置服务端端口
    server_addr.sin_port = htons(SERVER_PORT);

    //创建客户端
    client_socket_fd = socket(PF_INET,SOCK_STREAM,0);
    //判断是否创建成功
    if(client_socket_fd < 0){
       printf("create error!!!");
       return 1;
    }

    //连接服务器
    //参数一:哪一个客户端
    //参数二:连接服务器地址
    //参数三:地址大小
    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 ");

    //发送消息(向服务器发送内容)
    char buffer[BUFSIZ] = "Hello, Socket Server!";
    //参数一:指定客户端
    //参数二:指定缓冲区(冲那里数据读取)
    //参数三:实际读取的大小strlen(buffer)(其实读取到"\0"结束)
    //参数四:从哪里开始读取
    send(client_socket_fd,buffer,strlen(buffer),0);

    //关闭
    shutdown(client_socket_fd,SHUT_RDWR);
    printf("client--- end-----\n");

   return 0; 
}
4.4 执行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

小编们来看服务器创建成功! 下多了二个打印语句Hello, Socket Server!
,程序运营成功。

5、Android下Socket编程实例(jni落成)

小编们新建七个Java工程和3个Android工程,分别用来运营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("服务器Start...");
            while(true){
                //获取连接客户端
                Socket socket = serverSocket.accept();
                //读取内容
                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();
            //循环读取内容
            String content = null;
            while(true){
                try {
                    while((content = bufferedReader.readLine())!=null){
                      System.out.println("接收到了客户端:"+content);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
5.1.2 运行SocketServer
服务器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>
//第一步:导入Socket编程的标准库
//这个标准库:linux数据类型(size_t、time_t等等......)
#include<sys/types.h>
//提供socket函数及数据结构
#include<sys/socket.h>
//数据结构(sockaddr_in)
#include<netinet/in.h>
//ip地址的转换函数
#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;

    //服务端网络地址
    struct sockaddr_in  server_addr;

    //初始化网络地址
    //参数一:传变量的地址($server_addr)
    //参数二:开始位置
    //参数三:大小
    //初始化服务端网络地址
     memset(&server_addr,0,sizeof(server_addr));
    //AF_INET:TCP/IP协议、UDP
    //AF_ISO:ISO 协议
     server_addr.sin_family = AF_INET;
    //设置服务端IP地址(自动获取系统默认的本机IP,自动分配)
     server_addr.sin_addr.s_addr = inet_addr(server_ip);

     //设置服务端端口
     server_addr.sin_port = htons(server_port);

    //创建客户端
    client_socket_fd = socket(PF_INET,SOCK_STREAM,0);
    //判断是否创建成功
    if(client_socket_fd < 0){

       LOGE("create error!");
       return ;
    }

    //连接服务器
    //参数一:哪一个客户端
    //参数二:连接服务器地址
    //参数三:地址大小
    int con_result = connect(client_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if(con_result<0){
    LOGE("connect error!");

    return ;
    }

    //发送消息(向服务器发送内容)
    char buffer[BUFSIZ] = "Hello Socket Server!";
    //参数一:指定客户端
    //参数二:指定缓冲区(冲那里数据读取)
    //参数三:实际读取的大小strlen(buffer)(其实读取到"\0"结束)
    //参数四:从哪里开始读取
    send(client_socket_fd,buffer,strlen(buffer),0);

    //关闭
    shutdown(client_socket_fd,SHUT_RDWR);
    LOGI("client--- end-----");
        (*env)->ReleaseStringUTFChars(env, server_ip_jstr, server_ip);
   return ;

  }
5.2.3 调用主程序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编程详解