Linux网络编程


今天是6.2号,到6.14号需要提交《中期检查报告》、《学术报告》,现在论文需要大修,至少6.15中期答辩后再次提交,怎么也要在赶在6.24号入职之前定稿投出去。

修改小论文、修改《中期检查报告》,再写《学术报告》,最后制作PPT,一件一件事情搞吧......痛苦啊......

抽着时间把这篇论文补一下吧...

几个名词的说明:

  • 协议:通常指某一个协议,一般由某一个或者一组文件如rfc/draft来指定。
  • 协议族:指彼此相互关联的一组协议。
  • 协议栈:指某一组协议的关系以及该组协议的层次结构,一般有清晰的up/down依赖关系和上下行消息交互。

参考文献:

[1] 《嵌入式Linux开发教程(上册)》

[2] Linux Socket过程详细解释(包括三次握手建立连接,四次握手断开连接)

[3] TCP三次握手四次挥手详解

[4] bind:address already in use的深刻教训以及解决办法

[5] socket套接字详解(TCP与UDP)


1、网络基本概念

1.1 OSI模型

OSI模型(Open System Interconnection Model,开放系统互联模型)是一个由国际标准化组织提出的概念模型,试图提供一个使各种不同的计算机和网络在世界范围内实现互联的标准协议。

  • OSI模型将计算机网络体系划分为七层,每层都可以提供抽象良好的接口。了解OSI模型有利于理解实际上OSI模型互联网络的工业标准--TCP/IP协议。
  • OSI是一个理想化的模型,实际上的协议(比如TCP/IP)并不是严格按照此模型来做的。
  • OSI模型各层之间OSI模型关系和通信时的数据流如下图所示。

Linux网络编程_第1张图片

(1)物理层

物理层负责将最后的信息编码成电流脉冲或其他信号在网上传输。他有计算和网络介质之间的实际界面所组成,可定义电气信号、符号、线的状态和始终要求、数据编码以及数据传输用连接器。

如最常用的RS232规范就属于物理层,所有比物理层高的层都用过事先定义好的接口和它通信。

(2)数据链路层

数据链路层通过物理网络链路提供数据传输。不同的数据链路层定义了不同的网络和协议特征。其中包括物理编址网络拓扑结构错误校验数据帧序列以及流控

  • 物理编址(相对应的是网络编址)定义了设备在数据链路层的编址方式;
  • 网络拓扑结构定义了设备的物理连接方式,比如总线拓扑结构和环拓扑结构;
  • 错误校验向发生传输错误的上层协议告警;
  • 数据帧序列重新整理并传输序列以外的帧;
  • 流控可能延缓数据的传输,以使接收设备不会因为在某一时刻接收到超过其处理能力的信息流而崩溃。

数据链路层实际上由两部分组成:介质存取控制(Media Access Control,MAC逻辑链路控制(Logical Link COntrol,LLC

MAC描述在共享介质环境中如何进行调度、发送和接收数据。MAC确保信息跨链路的可靠传输,对数据传输进行同步,识别错误和控制数据的流向。

逻辑链路控制子层管理单一网络链路上设备间的通信。

(3)网络层

网络层负责在源和终点之间建立连接,按一般包括网络寻径,还可能包括流量控制、错误检查等。

相同MAC标准不同网段之间的数据传输一般只涉及数据链路层。而不同MAC标准之间的数据传输都涉及到网络层。网络层使不同类型的数据网络能够实现互联。

(4)传输层

传输层向高层提供可靠的端到端的网络数据流服务。传输层的功能一般包括流控多路传输虚电路管理以及差错检验和恢复

(5)会话层

会话层建立、管理和终止表示层和实体之间的通信会话。通信会话包括发生在不同网络应用层之间的服务请求和服务应答,这些请求与应答通过会话层的协议实现。他还包括创建检查点,是在通信发生中断的时候可以返回到以前的某个状态。

(6)表示层

表示层提供多种功能用于应用层数据编码和转化,以确保一个系统应用层发送信息可以被另一个系统应用层识别表示层的编码和转化包括公共数据表示格式、性能转化表示格式、公共数据压缩模式和公共数据加密模式

  • 公共数据表示格式就是标准的图像、声音和视频格式。通过使用这些标准的格式,不同类型的计算机系统可以相互交换数据;
  • 性能转化表示格式通过使用不同的文本和数据表示,在系统间交换信息,如ASCII码;
  • 公共数据压缩模式确保原始设备上被压缩的数据可以在目标设备上正确的解压;
  • 公共数据加密模式确保原始设备上加密的数据可以在目标设备上正确的解密。

表示层协议一般不与特殊的协议栈相关联,如QuickTime是Apple计算机视频和音频的标准,MPEG是ISO的视频压缩和编码标准。常见的图形图像格式如GIF、JPEG是不同静态图片压缩和编码标准。

(7)应用层

应用层是最接近用户的OSI层,OSI应用层与用户之间是通过软件直接相互作用的。

OIS应用层协议包括文件的传输、访问以及管理协议(FTAM),以及文件虚拟终端协议(VIP)和公共管理系统信息(CMIP)等。

  • 应用层不是由实际应用的软件所组成的,而是由一系列访问网络资源的API(Application Program Interface,应用程序接口)组成;
  • 应用层的功能一般包括标识通信伙伴定义资源的可用性同步通信
    • 因为可能丢失通信伙伴,所以应用层必须为传输数据的应用子程序定义通信伙伴的标识和可用性;
    • 定义资源可用性时,应用层为了请求通信而必须判定是否有足够的网络资源;
    • 在同步通信中,所用应用程序之间的通信都需要应用层的协同操作。

1.2 TCP/IP协议基本概念

(1)网络接口层

网络接口层包括用于协作IP数据在已有的网络介质上传输的协议。实际上TCP/IP标准并不定义与OSI数据链路层和物理层相对应的功能。相反,它定义像地址解析协议这样的协议,提供TCP/IP协议的数据结构和实际物理硬件之间的接口。

(2)网络层

网络层对应OSI七层参考模型的网络层。本层包含IP协议、RIP协议,负责数据的包装、寻址和路由。同时还包含网间控制报文协议(ICMP),用来提供网络诊断信息。

(3)传输层

传输层对应于OSI的传输层,提供两种端到端的通信服务。其中TCP协议提供可靠的数据流服务,UDP协议提供不可靠的用户数据流服务。

(4)应用层

应用层对应OSI的应用层和表示层。因特网的应用层协议包括Finger,Whois、FTP(文件传输协议)、Gopher、HTTP(超文本出阿叔协议)、TELNET(远程终端协议)、SMTP(简单邮件传输协议)、IRC(因特网中断会话)、NNTP(网络新闻传输协议)等。

(5)TCP/IP协议族常用协议

IP(Internet Protocol,网际协议)是网间层的主要协议,任务是在源地址和目的地之之间传输数据。IP协议只是尽最大努力来传输数据包,并不保证所有的包都可以传输到目的地,也不保证数据包的顺序和位移。

IP定义了TCP/IP的地址寻址方法、以及路由的规则。现在广泛使用的IP协议有IPv4和IPv6。

  • IPv4使用32位二进制整数作为地址,一般使用点分十进制方式表示,比如192.168.0.3。IP地址由两部分组成,即网络号和主机号。故一个完整的IPv4地址往往表示为192.168.0.3/24或192.168.0.3/255.255.255.0的形式。
  • IPv6是为了解决IPv4地址耗尽和其他一些问题而研发的新版本IP。使用128位二进制表示地址,通常用冒号分割的十六进制表示,并且可以省略其中一串连续的0,比如:fe80:200:1ff:fe00:1。IPv6提供了一些IPv4没有的新特性,并且又几乎用不完的地址,但目前还在部署当中,国内除高校和科研机构外并不常用,故暂不讨论。
  1. TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的通信协议TCP具有端口号的概念,用来表示同一个低智商的不同应用。藐视TCP的标准文档是RFC793。
  2. UDP(User Datagram Protocol,用户数据报协议)是一个面向数据报的传输层协议。UDP的传输是不可靠的,简单说就是发了不管数据是否完整无误到达目标地址,它同TCp一样用来表示本地应用的端口号。所以使用UDP的应用能够容忍一定的数据错误和丢包,但是对于传输性能敏感(比如流媒体,DNS等)。描述UDP的标准文档是RFC768。
  3. FTP(File Transfer Protocol,文件传输协议)是用来进行文件传输的标准协议,FTP基于TCP,使用端口号20来传输数据,21来控制信息。其描述文档是RFC959。
  4. TFTP(Trivial File Transfer Protocol,简单文件传输协议)是一个简化的文件传输协议,其设计非常简单,通过少量存储器就能轻松实现,所以一般被用来通过网络引导计算机过程中的传输引导文件等小文件。在传输大文件时建议不使用TFTP。
  5. HTTP(HyperText Transfer Protocol,超文本传输协议)是现在广为流传的WEB网络基础,GTTPS是HTTP的加密安全版本。协议通过TCP传输,HTTP默认使用端口80,HTTPS使用端口43。
  6. SSH(Secure Shell,安全Shell),因为传统的网络服务程序(比如TELNET)在本质上都不安全,都是明文传输数据和用户信息(包括密码),所以SSH被开发出来避免这些问题,他其实是一个协议框架,有点大浪的扩展冗余能力,并且提供了加密压缩的通道,可以为其他协议所使用。
  7. ......

1.3 字节序

在计算机中,多字节的对象都被表示为连续的字节序列,而存储在内部的排列有两个通用的规则,一个多位的整数将按照其存储地址的最低或最高字节排列。如果最低有效字节在最高有效字节的前面,则称为小端序,反之称为大端序

比如对于0x12345678这样的32进制数,需要4个字节。

Linux网络编程_第2张图片

 

在网络应用中,字节序是必须要考虑的一个因素,因为不同机器类型可能采用不同标准的字节序,所以均需按照网络标准转化。网络传输的标准叫做网络字节序,实际上是大端序,而常用的X86或者ARM往往都是小端序,ARM的字节序实际上是可配置的,但一般都是配置为小端

在网络编程中不应该假设自己的程序运行的主机字节序,应当使用htonl/htons/ntohs/ntohl之类的函数在网络字节序和主机字节序之间进行转换其中h代表host,就是本地主机的表示形式。n代表network,表示网络上传输的字节序;s和l代表类型short和long。

1.4客户机/服务器模型

网络上进行通信的各个端点,很多时候是遵循客户机/服务器模型的。

服务器端特征如下:

  • 被动通信;
  • 是中等待来自客户端的请求;
  • 自己参与通信的网络接口和端口必须确定;
  • 处理客户端的请求后将结果(响应)返回给客户端

客户端的特征如下:

  • 主动通信;
  • 需要发起请求;
  • 自己参与通信的网络接口和端口可以不确定;
  • 发情请求后需要等待服务器响应结果。

2、编程接口BSD(BSD UNIX) Socket

本地的进程间通信(IPC)有很多种方式,可以总结为下面4类:

  • 消息传递(管道、FIFO、消息队列)

  • 同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)

  • 共享内存(匿名的和具名的)

  • 远程过程调用(Solaris门和Sun RPC)

网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起。在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。

2.1 Socket简介

用Socket能够实现网络上不同主机之间或同一主机不同对象之间的数据通信,所以,现在Socket已经是一类通信接口的集合。广分为网络Socket本地Socket

(1)本地Socket在Linux上包括UNIX Domain Socket和Netlink两种。UNIX Domain Socket主要用于进程间通信,Netlink则用于用户空间和内核空间通讯。

(2)网络Socket支持很多不同的协议,以下主要基于TCP/IP协议族中的TCP和UDP协议的网络编程。谈论仅限于IPv4网络的协议族和地址表示。

2.2 基础数据结构和函数

1. 地址表示数据结构

IP协议使用的地址描述数据结构在netinet/in.h中定义。

Linux下该结构的典型说明如下:

/* Structure describing an Internet (IP) socket address. */
#define __SOCK_SIZE__	16		/* sizeof(struct sockaddr)	*/
struct sockaddr_in {
  __kernel_sa_family_t	sin_family;	/* Address family		*/
  __be16		sin_port;	/* Port number	端口号		*/
  struct in_addr	sin_addr;	/* Internet address	IP地址	*/

  /* Pad to size of `struct sockaddr'. */
  unsigned char		__pad[__SOCK_SIZE__ - sizeof(short int) -
			sizeof(unsigned short int) - sizeof(struct in_addr)];
};

sin_family该字段被赋值为AF_INET,表示IPv4协议族。

sin_port为端口号,通常1024号以下的端口需要root权限才可以使用。另外有很多已经约定好了对应特定服务的端口号,具体可以查看/etc/services,在选用自定义协议的端口号时,尽量不要和已知服务重合。

struct in_addr是在通信时使用的IP地址结构,Linux下的原型如下:

/* Internet address. */
typedef __u32 __bitwise __be32;
struct in_addr {
	__be32	s_addr;
};

只要填充这个结构体,这是一个32位二进制整数代表的IP地址,对应一个本机有效网络接口的地址,也可以调重围INADRR_ANY,代表本机可以使用的网络地址。大部分时候都是用INADDR_ANY来填充此处。

一段典型的填充IP地址数据结构的代码如下:

...
struct sockaddr_in addr;
...

addr.sin_family    = AF_INET;    /* 使用IPv4协议族 */
addr.sin_port      = htons(80);  /* 设置端口号为80 */
addr.sin_addr.s_addr    = inet_addr("192.168.0.1");    /* 设置IP地址为192.168.0.1 */

注意:

sin_port和sin_addr.s_addr两个值都是多字节的整数,Socket规定这里必须使用网络字节序。

2. 网络字节序和本地字节序之间的转换

有四个基本函数,可以实现网络字节序和本地字节序之间的转换。函数头文件、原型及函数功能说明如下:

#include 

/* 32位整数从主机字节序转换为网络字节序 */
uint32_t htonl(uint32_t hostlong);
/* 16位整数从主机字节序转换为网络字节序 */
uint16_t htons(uint16_t hostshort);
/* 32位整数从网络字节序转换为主机字节序 */
uint32_t ntohl(uint32_t netlong);
/* 16位整数从网络字节序转换为主机字节序 */
uint16_t ntohs(uint16_t netshort);

3. 主机名和地址转换函数

在实际网络编程中,往往需要在IP地址的点分十进制表示和二进制表示之间相互转化,也需要进行主机名和地址转换,因此系统提供了一系列函数,一般需要包含头文件:

#include 
#include 

(1)in_addr_t inet_addr(const char* cp)

将一个点分十进制IP地址转换成in_addr_t类型,该类型实际上是一个32位无符号整数,实际上就是:
 

struct in_addr {
	__be32	s_addr;
};

中s_addr域的数据类型。注意这个二进制表示的IP地址规定的是网络字节序。

192.168.0.1在PC上会被转换成0x0100A8C0

(2)char* inet_ntoa(struct in_addr in)

此函数可以将结构体struct in_addr中的二进制IP地址转换成一个点分十进制表示的字符串,返回这个字符串的首指针。他所返回的缓冲区是静态分配的,在并发或者异步使用时要小心,缓冲区可能随时被其它调用改写。

如下调用,会将一个网络字节序二进制无符号32位整数表示的IP地址0x0100A8C0转换为点分十进制表示"192.168.0.1":

...
char *str;
struct in_addr addr = {
    s_addr = 0x0100A8C0,
}
...
str = inet_ntoa(addr);
...

(3)通过主机名获取IP地址

实际应用时,很多时候得到的通信另一方是主机名,所以需要将主机名转换为IP地址。传统上,有两个函数声明在netdb.h中进行这个操作。其中一个是gethostbyname()函数,其原型如下:

struct hostent *gethostbyname(const char* name);

直接根据主机名字符串返回一个struct hostent结构。此返回的数据结构有可能是静态分配的。

其中struct hostent在Linux下的原型如下:

 

 

2.3 BSD Socket常用操作

socket即是一种特殊的文件,一些socket函数就是对其进行的操作 (读/写IO、打开、关闭)。下面介绍一些基本的socket操作函数。

1. 创建Socket 

在进行Socket通信之前,一般调用socket()函数来创建一个Socket通信端点。socket原型如下:

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

socket函数对应于普通文件的打开操作普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

参数说明:

  • domain代表这个socket所使用的地址类型,对于讨论的IPv4协议的IP地址,可以用使用AF_INET,也可以使用PF_INET。实际上这两个值是相等的,但是通常大部分人更习惯使用AF_INET。
  • type代表了这个Socket的类型,这里讨论的是面向流的(TCP)和面向数据报的(UDP),分别取值为SOCK_SREAM和SOCK_DGRAM。
  • protocol是协议类型,常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

函数成功返回一个有效的文件描述符,出错时返回-1。

调用socket()创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

2. 绑定地址和端口 bind()

创建了Socket之后,可以调用bind()函数来讲这个Socket绑定到特定的地址和端口上进行通信,函数原型如下:

int bind(int socket, const struct sockaddr *address, socklent address_len);

参数说明如下:

  • socket是指向socket的有效文件描述符;
  • address参数就是一个指向struct sockaddr结构的指针,根据不同的协议可以有不同的具体结构,对于IPv4,就是struct sockaddr_in;对于IP6,是struct sockaddr_in6,但是在调用函数的时候需要强制转换一下这个指针避免警告;
  • address_len,此处应该指明使用的地址数据结构的长度。

函数调用成功时返回0,否则返回-1。

通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过这个地址来接连服务器;而客户端就不用指定,由系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

3. 连接服务器 connet()

对于客户机,使用TCP协议时,在通信前必须调用connect()函数连接到服务器的特定通信端口才能正确进行通信。

对于使用UDP协议的客户机,这个步骤是可选项。如果使用了connect(),在此之后可以不需要指定数据报的目的地址而直接发送,否则每次发送数据均需要指定数据报的目的地址和端口。

connect()函数原型如下:

int connect(int socket, const sockaddr *address, socklent address_len);

其中所有参数的意义和bind()函数的参数一致。客户端通过调用connect函数来建立与TCP服务器的连接。

函数成功返回0,否则返回-1。

4. 设置Socket为监听模式 listen()

基于TCp协议的服务器,需要调用listen()函数将其socket设置成被动模式,等待客户机的链接。其函数原型如下:

int listen(int socket, int backlog);

参数说明:

  • socket与前面函数都一样;
  • backlog指等待的队列长度,但实际上队列可能大于这个数字,通常取5。

socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

函数调用成功返回0,失败返回-1。

5. 接受连接 accept()

TCP服务器还需要调用accept()来处理客户机的连接请求,函数原型如下:

int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);

参数说明:

  • socket和前面的函数都一样;
  • 第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址
  • 第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。存储上一个参数返回地址数据结构的长度。

函数成功返回一个有效的文件描述符,此文件描述符指向(成功与客户机建立链接可以进行数据交换的Socket。服务器程序使用文件描述符来与客户端进行后续的交互。

6. 数据读写函数

(1)读数据函数

以下函数均可以读Socket数据:read()、recv()、recvfrom()、recvmsg()。函数原型分别如下:

#include 
ssize_t read(int fd, void *buf, size_t count);

/*********************************************************/
#include 
#include 
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

(2)写数据函数

相应的write()、send()、sendto()和sendmsg()都可以发送数据到socket,功能和原型都类似于读数据函数,函数原型如下:

#include 
ssize_t write(int fd, const void *buf, size_t count);

/****************************************/
#include 
#include 
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

3、socket中TCP的三次握手建立连接

三次握手(Three-way Handshake),是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。

三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息在socket编程中,客户端执行connect()时。将触发三次握手。

tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:

  • 客户端向服务器发送一个SYN J
  • 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
  • 客户端再向服务器发一个确认ACK K+1

image

  1. 当客户机调用connect( )时,触发了连接请求,向服务器发送了SYN J包,这时connect()进入阻塞状态;
  2. 服务器监听到连接请求,即收到SYN J包,调用accept( )函数接收请求向客户端发送SYN K,ACK J+1,这时accept( )进入阻塞状态;
  3. 客户端收到服务器的SYN K ,ACK J+1之后,这时connect( )返回,并对SYN K进行确认;
  4. 服务器收到ACK K+1时,accept( )返回,至此三次握手完毕,连接建立。

总结:客户端的connect( )在三次握手的第二次返回,而服务器端的accept( )在三次握手的第三次返回。

4、socket中TCP的四次握手释放连接

image

  • 某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;

  • 另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;

  • 一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;

  • 接收到这个FIN的源发送端TCP对它进行确认。

这样每个方向上都有一个FIN和ACK。

5、实例:TCP/UDP ECHO服务器

5.1 面向流的Socket

(1)服务器 server.c

#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
#include "mysocket.h"
int main(){
        //create socket
        int server_socket_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_socket_fd < 0)
                printf("create socket error!\n");
        //bind
        struct sockaddr_in server_addr,client_addr;
        socklen_t serversock_len = sizeof(server_addr);
        (void)memset(&server_addr,0,serversock_len);
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        server_addr.sin_port = htons(PORT);
        int on = 1;
        if(setsockopt(server_socket_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(int)) < 0)
                printf("setsockopt error!\n");
        int bind_fd = bind(server_socket_fd,(const struct sockaddr*)&server_addr,sizeof(server_addr));
        if(bind_fd < 0)
                printf("bind error!\n");
        //listen
        if(listen(server_socket_fd,5) < 0)
                printf("create listen error!\n");
        printf("connect..\n");
        //accept
//      while(1){
                printf("wait connect...\n");
                int connect_sock_fd = accept(server_socket_fd,(struct sockaddr*)&client_addr,&serversock_len);
                printf("connect success!\n");
                //do something
                int fork_fd = fork();
                if(fork_fd < 0){
                        printf("fork error!\n");
                        close(server_socket_fd);
                }
                else if(fork_fd == 0){
                        printf("get siganel!\n");
                        printf("=============\n");
                        char buf[10];
                        if(read(connect_sock_fd,buf,sizeof(buf)/sizeof(buf[0])) < 0)
                                printf("read error!\n");
                        printf("recv data = %s\n",buf);
                }
                else{
                        wait(0);
//                      continue;
                }
//              printf("close socket!\n");
                close(server_socket_fd);
//      }
}

(2)客户机 client.c

#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
#include "mysocket.h"
#define SERVER_IP    "192.168.0.3"
int main(int argc,char** argv)
{
        struct sockaddr_in server_addr,client_addr;
/*      if(argc !=2){
                printf("usage: ./client \n");
                exit(0);
        }
        */
        int client_socket_fd = socket(AF_INET,SOCK_STREAM,0);
        if(client_socket_fd < 0)
                printf("client socket create error!\n");
        (void)memset(&client_addr,0,sizeof(client_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(PORT);
        server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
        if(bind(client_socket_fd,(const struct sockaddr*)&client_addr,(socklen_t)sizeof(struct sockaddr_in)) < 0)
                printf("bind error!\n");
        if(connect(client_socket_fd,(const struct sockaddr*)&server_addr,(socklen_t)sizeof(client_addr)) < 0)
                printf("create connect error!\n");

        printf("connect suceess!\n");
        char send[6] = {'h','e','l','l','o','\0'};
        int j = 5;
        printf("count = %d\n",sizeof(send)/sizeof(send[0]));
        printf("count!\n");
        if(write(client_socket_fd,send,sizeof(send)/sizeof(send[0])+1) < 0)
                printf("write error!\n");
        printf("write success!\n");
}

#include "mysocket.h"

#ifndef _MYSOCKET_H
#define _MYSOCKET_H
#include 
#include 
#define PORT 5555

#endif

Ubuntu输出:

Linux网络编程_第3张图片

ARM开发板输出:

5.2 面向数据报的Socket

(1)服务器

 

(2)客户机

 

 

 

 

 

你可能感兴趣的:(Linux,C及Shell编程)