linux系统开发: 学习Linux下网络编程(linux开发板)

网友投稿 1494 2022-05-30

第一章 TCP网络编程

1.1 socket创建套接字

#include

#include

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

功能

创建网络套接字,用于网络通信使用,类似于文件操作的open函数。该函数在服务器和客户端都会用到。

参数

int domain :网络协议版本指定。

AF_INET        IPv4 Internet protocols

AF_INET6       IPv6 Internet protocols

int type:指定通信协议类型。

SOCK_STREAM 表明我们用的是TCP协议 (字节流)

SOCK_DGRAM 表明我们用的是UDP协议 (数据报)

int protocol:指定通信协议类型。Type参数已经指定了协议,该参数直接填0即可!

返回值

成功返回网络套接字,与open函数返回值类似。

示例

Clientfd = socket(PF_INET,SOCK_STREAM,0);

1.2 bind绑定IP-端口

#include           /* See NOTES */

#include

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

功能

创建服务器。该函数在服务器端使用。

参数

int sockfd : 网络套接字

const struct sockaddr *addr  :填充创建服务器所需的地址信息,详细的成员看1.3章节。

socklen_t addrlen :地址长度,就是该结构体的大小。使用sizeof函数进行计算。

返回值

0表示成功,-1表示失败!

1.3 struct sockaddr地址结构体

1.3.1 结构体成员解析

在实际填充参数的过程中,struct sockaddr结构体被struct sockaddr_in结构体代替。struct sockaddr_in结构体比struct sockaddr可读性强一些,填充参数比较好理解。

struct sockaddr_in和struct sockaddr大小相同。在填充结构体的时候为了方便填充参数,使用struct sockaddr_in结构体,给函数赋值的时候需要强制转换为struct sockaddr类型的结构体。因为底层函数最终还是使用struct sockaddr类型的结构体。

struct sockaddr结构体成员:

struct sockaddr {undefined

sa_family_t sa_family;      //网络协议版本。填写:AF_INET 或者 AF_INET6。

char        sa_data[14];   //IP地址和端口

}

struct sockaddr_in结构体成员:

查看IPV4协议帮助文档:# man  7  ip

struct sockaddr_in {undefined

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   存放IP地址的结构体*/

};

/* Internet address. */

struct in_addr {undefined

uint32_t       s_addr;     /* address in network byte order IP地址 */

};

1.3.2 端口号赋值

计算机数据存储有两种字节优先顺序: 高位字节优先和低位字节优先。 Internet 上数据以高位字节优先顺序在网络上传输, 所以对于在内部是以低位字节优先方式存储数据的机器, 在 Internet 上传输数据时就需要进行转换, 否则就会出现数据不一致。

普通人用的桌面电脑,只要是Intel或AMD的x86/x64架构就一定是小端字节序。

外很多ARM CPU可以选择数据指令字节序,不过通常也都是运行小端字节序(比如我们的智能手机)。

网络设备,像PowerPC核心的一些路由器,默认运行大端字节序。

下面是几个字节顺序转换函数:

Linux系统开发: 学习Linux下网络编程(linux开发板)

·htonl(): 把 32 位值从主机字节序转换成网络字节序

·htons(): 把 16 位值从主机字节序转换成网络字节序

·ntohl(): 把 32 位值从网络字节序转换成主机字节序

·ntohs(): 把 16 位值从网络字节序转换成主机字节序

函数原型

#include

uint32_t htonl(uint32_t hostlong);

uint16_t htons(uint16_t hostshort);

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);

网际协议在处理这些多字节整数时,使用大端字节序。

在主机本身就使用大端字节序时,这些函数通常被定义为空宏。

给struct sockaddr_in结构体的端口成员赋值的时候就需要用到以上大端转小端函数进行转换!

示例:

/*结构体成员赋值*/

tcp_server.sin_family=AF_INET; //IPV4协议类型

tcp_server.sin_port=htons(tcp_server_port);//端口号赋值,将本地字节序转为网络字节序

tcp_server.sin_addr.s_addr=INADDR_ANY; //将本地IP地址赋值给结构体成员

//inet_addr("192.168.18.3"); //IP地址赋值

1.3.3 IP地址赋值

struct sockaddr_in结构体存放IP地址的成员是struct in_addr 结构体类型,底层存放地址的成员是一个无符号int类型,而我们生活中的IP地址是使用xxx.xxx.xxx.xxx 这种格式表示的。比如:192.168.1.1。 在赋值的时候就需要进行将”192.168.1.1”这种格式转为无符号int类型才能进行赋值。

以下是几个IP格式转换函数:

将字符串类型IP转为in_addr_t类型(unsigned int)返回。

in_addr_t inet_addr(const char *cp);

示例:

Serveraddr.sin_addr.s_addr = inet_addr("192.168.18.3");

使用字符串类型的IP直接给结构体成员赋值

int inet_aton(const char *cp, struct in_addr *inp);

示例:

inet_aton(“192.168.18.3”,&Clientaddr.sin_addr);

将结构体里的IP地址成员转为字符串类型返回

char *inet_ntoa(struct in_addr in);

该函数与上面两个函数功能刚好相反。是将整型的IP转为字符串类型!

1.3.4 本地计算机大小端判断

首先说明,电脑大小端指的是一种存储模式。

为什么有大小端:

在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题,因此就导致了大端存储模式和小端存储模式。

大小端定义:

大端模式(Big-endian),是指数据的高字节,保存在内存的低地址中,而数据的低字节,保存在内存的高地址中。

小端模式(Little-endian),是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。

直接来看一个图,详细说明大小端:

例子:int i = 0x12345678 两种模式存入内存:

4. 判断大小端的C语言代码

#include

int CheckSystem()

{

union check

{

int i;

char ch;

}c;

c.i=1;

return (c.ch==1);

}

int main()

{

int check=CheckSystem();

if(check==1)

printf("当前系统为小端\n");

else

printf("当前系统为大端\n");

return 0;

}

///

// 公用的四个字节地址 :0x1001 -> 0x1002 -> 0x1003 -> 0x1004

//   小端来说  赋值 1 : 0x01      0x00      0x00      0x00

//   大端来说  赋值 1 : 0x00      0x00      0x00      0x01

//也就是说存数据都是从低地址存放 一个char字节,

//他和int开始的地址是一样的 读的话 还是从低字节向高字节完整的读取

1.4 listen监听端口的数量

#include           /* See NOTES */

#include

int listen(int sockfd, int backlog);

功能

设置服务器需要监听的端口数量。决定了能够同时响应连接的服务器数量。

返回值

成功返回0,失败返回-1。

服务器创建,函数调用顺序:

示例:listen(Serverfd,10)

1.5 accept 等待客户端连接

#include           /* See NOTES */

#include

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

功能

以阻塞的形式等待客户端连接。

参数

struct sockaddr *addr  :存放已经连接的客户端信息。传入一个结构体地址。

socklen_t *addrlen    :表示客户端的结构体大小。该大小需要我们指定,客户端连接成功然后再判断是否与填写的大小一致。

返回值

成功将返回客户端的网络套接字。错误返回-1。

示例:

struct sockaddr_in Clientaddr;

len = sizeof(struct sockaddr);

Clientfd = accept(Serverfd,(struct sockaddr *)&Clientaddr,&len);

1.6 connect连接服务器

#include           /* See NOTES */

#include

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

功能

连接到指定服务器。该函数在客户端使用。

参数

int sockfd :socket函数的网络套接字。

const struct sockaddr *addr :服务器的IP地址信息。 参考:1.2节和1.3.节

socklen_t addrlen :结构体的大小。

返回值

成功返回0,错误返回-1。

示例

connect(Clientfd,(struct sockaddr *)&Clientaddr,sizeof(struct sockaddr));

1.7 send/ recv网络数据收发

#include

#include

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

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

功能

客户端与服务器之间的数据收发。

参数

const void *buf 、void *buf :读写的缓冲区。

int flags :填0。

以上两个函数可以使用write和read函数替换。

1.8 shutdown关闭连接

#include

int shutdown(int sockfd, int how);

返回

0—成功,-1—失败。

参数how的值:

SHUT_RD:关闭连接的读这一半,不再接收套接口中的数据且留在套接口缓冲区中的数据都作废。进程不能再对套接口任何读函数。调用此函数后,由TCP套接口接收的任何数据都被确认,但数据本身被扔掉。

SHUT_WR:关闭连接的写这一半,在TCP场合下,这称为半关闭。当前留在套接口发送缓冲区中的数据都被发送,后跟正常的TCP连接终止序列。此半关闭不管套接口描述字的访问计数是否大于0。进程不能再执行对套接口的任何写函数。

SHUT_RDWR:连接的读这一半和写这一半都关闭。这等效于调用shutdown两次:第一次调用时用SHUT_RD,第二次调用时用SHUT_WR。

shutdown(tcp_client_fd,SHUT_WR);  //TCP半关闭,保证缓冲区内的数据全部写完

直接强制关闭连接示例:

int close(int fd);

1.9 查看Linux系统当前的网络连接

在/proc/net/tcp目录下面保存了当前系统所有TCP链接的状态信息。

查看示例:

[root@wbyq FileSend2]# cat /proc/net/tcp

sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode

0: 00000000:006F 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 13264 1 c16ac5c0 99 0 0 10 -1

1: 00000000:DA10 00000000:0000 0A 00000000:00000000 00:00000000 00000000    29        0 13592 1 c16ac0c0 99 0 0 10 -1

2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 14400 1 c16acac0 99 0 0 10 -1

3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 13851 1 c142f080 99 0 0 10 -1

4: 0100007F:0019 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 14753 1 c142fa80 99 0 0 10 -1

5: 813DA8C0:A019 49AAC3CB:0522 01 00000000:00000000 00:00000000 00000000     0        0 123641 1 c142f580 20 3 18 10 -1

说明:  这里的IP地址信息和端口号都是使用十六进制保存的。

813DA8C0:A019 49AAC3CB:0522

查看网络状态连接:

[root@wbyq FileSend2]# netstat -ntp

Active Internet connections (w/o servers)

Proto Recv-Q Send-Q   Local Address         Foreign Address       State            PID/Program name

tcp        0  0     192.168.61.129:40985     203.195.170.73:1314    ESTABLISHED   20955/./app_c

从上面可得到的信息:

连接类型: TCP协议

本地IP地址和端口号: 192.168.61.129:40985

与其通信的远程IP地址和端口号: 203.195.170.73:1314

状态: ESTABLISHED(已建立的连接)

进程PID号与应用程序名称: 20955/./app_c

socket网络连接的状态如下

1、LISTENING状态

FTP服务启动后首先处于侦听(LISTENING)状态。

2、ESTABLISHED状态

ESTABLISHED的意思是建立连接。表示两台机器正在通信。

3、CLOSE_WAIT

对方主动关闭连接或者网络异常导致连接中断,这时我方的状态会变成CLOSE_WAIT 此时我方要调用close()来使得连接正确关闭

4、TIME_WAIT

我方主动调用close()断开连接,收到对方确认后状态变为TIME_WAIT。TCP协议规定TIME_WAIT状态会一直持续2MSL(即两倍的分 段最大生存期),以此来确保旧的连接状态不会对新连接产生影响。处于TIME_WAIT状态的连接占用的资源不会被内核释放,所以作为服务器,在可能的情 况下,尽量不要主动断开连接,以减少TIME_WAIT状态造成的资源浪费。

目前有一种避免TIME_WAIT资源浪费的方法,就是关闭socket的LINGER选项。但这种做法是TCP协议不推荐使用的,在某些情况下这个操作可能会带来错误。

5、SYN_SENT状态

SYN_SENT状态表示请求连接,当你要访问其它的计算机的服务时首先要发个同步信号给该端口,此时状态为SYN_SENT,如果连接成功了就变为 ESTABLISHED,此时SYN_SENT状态非常短暂。但如果发现SYN_SENT非常多且在向不同的机器发出,那你的机器可能中了冲击波或震荡波 之类的病毒了。这类病毒为了感染别的计算机,它就要扫描别的计算机,在扫描的过程中对每个要扫描的计算机都要发出了同步请求,这也是出现许多 SYN_SENT的原因。

根据TCP协议定义的3次握手断开连接规定,发起socket主动关闭的一方 socket将进入TIME_WAIT状态,TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),在Windows下默认为4分钟,即240秒,TIME_WAIT状态下的socket不能被回收使用. 具体现象是对于一个处理大量短连接的服务器,如果是由服务器主动关闭客户端的连接,将导致服务器端存在大量的处于TIME_WAIT状态的socket, 甚至比处于Established状态下的socket多的多,严重影响服务器的处理能力,甚至耗尽可用的socket,停止服务. TIME_WAIT是TCP协议用以保证被重新分配的socket不会受到之前残留的延迟重发报文影响的机制,是必要的逻辑保证.

第二章 UDP网络编程

2.1 UDP协议创建流程

2.2 数据报收发函数

2.2.1 recvfrom函数

UDP使用recvfrom()函数接收数据,他类似于标准的read(),但是在recvfrom()函数中要指明数据的目的地址。

#include

#include

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr * from, size_t *addrlen);

返回值

成功返回接收到数据的长度,负数失败

前三个参数等同于函数read()的前三个参数,flags参数是传输控制标志。最后两个参数类似于accept的最后两个参数(接收客户端的IP地址)。

示例:

/*阻塞方式接收数据*/

int len=0;

char buff[1024];

size_t addrlen=sizeof(struct sockaddr);

while(1)

{undefined

len=recvfrom(socketfd,buff,1024,0,(struct sockaddr *)&ClientSocket,&addrlen);

buff[len]='\0';

printf("Rx: %s,len=%d\n",buff,len);

printf("数据发送方IP地址:%s\n",inet_ntoa(ClientSocket.sin_addr));

printf("数据发送方端口号:%d\n",ntohs(ClientSocket.sin_port));

}

2.2.2 sendto函数

UDP使用sendto()函数发送数据,他类似于标准的write(),但是在sendto()函数中要指明目的地址。

#include

#include

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr * to, int addrlen);

返回值

成功返回发送数据的长度,失败返回-1

前三个参数等同于函数read()的前三个参数,flags参数是传输控制标志。参数to指明数据将发往的协议地址,他的大小由addrlen参数来指定。

示例:

/*向UDP协议服务器发送数据*/

ServerSocket.sin_family=PF_INET; //协议

ServerSocket.sin_port=htons(PROT); //端口

ServerSocket.sin_addr.s_addr=inet_addr(argv[1]);  //表示服务器的IP地址

bzero(ServerSocket.sin_zero,8); //初始化空间

char buff[]="1234567890";

int len=0;

while(1)

{undefined

len=sendto(socketfd,buff,strlen(buff),0,(const struct sockaddr*)&ServerSocket,sizeof(struct sockaddr));

printf("Tx: %d\n",len);

sleep(1);

}

第三章 设置Socket套接字属性

3.1 函数原型介绍

#include           /* See NOTES */

#include

int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

参数

sockfd:标识一个套接口的描述字。

level:选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次。

optname:需设置的选项。

optval:指针,指向存放选项值的缓冲区。

optlen:optval缓冲区的长度。

3.2 属性功能注释

setsockopt()函数用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。

setsockopt()支持的选项定义位置:/usr/include/asm-generic/socket.h

#ifndef __ASM_GENERIC_SOCKET_H

#define __ASM_GENERIC_SOCKET_H

#include

/* For setsockopt(2) */

#define SOL_SOCKET  1

#define SO_DEBUG      1

#define SO_REUSEADDR    2

#define SO_TYPE          3

#define SO_ERROR       4

#define SO_DONTROUTE    5

#define SO_BROADCAST    6

#define SO_SNDBUF    7

#define SO_RCVBUF    8

#define SO_SNDBUFFORCE       32

#define SO_RCVBUFFORCE       33

#define SO_KEEPALIVE      9

#define SO_OOBINLINE       10

#define SO_NO_CHECK       11

#define SO_PRIORITY 12

#define SO_LINGER      13

#define SO_BSDCOMPAT   14

/* To add :#define SO_REUSEPORT 15 */

#ifndef SO_PASSCRED /* powerpc only differs in these */

#define SO_PASSCRED        16

#define SO_PEERCRED        17

#define SO_RCVLOWAT      18

#define SO_SNDLOWAT      19

#define SO_RCVTIMEO       20

#define SO_SNDTIMEO       21

#endif

/* Security levels - as per NRL IPv6 - don't actually do anything */

#define SO_SECURITY_AUTHENTICATION           22

#define SO_SECURITY_ENCRYPTION_TRANSPORT   23

#define SO_SECURITY_ENCRYPTION_NETWORK               24

#define SO_BINDTODEVICE      25

/* Socket filtering */

#define SO_ATTACH_FILTER    26

#define SO_DETACH_FILTER    27

#define SO_PEERNAME               28

#define SO_TIMESTAMP             29

#define SCM_TIMESTAMP         SO_TIMESTAMP

#define SO_ACCEPTCONN          30

#define SO_PEERSEC           31

#define SO_PASSSEC           34

#define SO_TIMESTAMPNS                35

#define SCM_TIMESTAMPNS            SO_TIMESTAMPNS

#define SO_MARK                         36

#define SO_TIMESTAMPING              37

#define SCM_TIMESTAMPING  SO_TIMESTAMPING

#define SO_PROTOCOL                38

#define SO_DOMAIN            39

#define SO_RXQ_OVFL                40

#endif /* __ASM_GENERIC_SOCKET_H */

setsockopt()支持下列选项。其中“类型”表明optval所指数据的类型。

选项

类型

意义

SO_BROADCAST

BOOL

允许套接口传送广播信息。

SO_DEBUG

BOOL

记录调试信息。

SO_DONTLINER

BOOL

不要因为数据未发送就阻塞关闭操作。设置本选项相当于将SO_LINGER的l_onoff元素置为零。

SO_DONTROUTE

BOOL

禁止选径;直接传送。

SO_KEEPALIVE

BOOL

发送“保持活动”包。

SO_LINGER

struct linger FAR*

如关闭时有未发送数据,则逗留。

SO_OOBINLINE

BOOL

在常规数据流中接收带外数据。

SO_RCVBUF

int

为接收确定缓冲区大小。

SO_REUSEADDR

BOOL

允许套接口和一个已在使用中的地址捆绑(参见bind())。

SO_SNDBUF

int

指定发送缓冲区大小。

TCP_NODELAY BOOL

禁止发送合并的Nagle算法。

3.3 设置socket具有广播特性

发送UDP数据报的时候,设置socket具有广播特性:(默认情况下socket不支持广播特性)

const int opt = 1;

//设置该套接字为广播类型,

int nb = 0;

nb = setsockopt(client_fd, SOL_SOCKET, SO_BROADCAST, (char *)&opt, sizeof(opt));

if(nb == -1)

{undefined

printf("设置广播类型错误.\n");

}

3.4 设置socket发送和接收的缓冲区大小。

系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据和接收数据量比较大,可以设置socket缓冲区。

// 接收缓冲区

int nRecvBuf=20*1024;//设置为20K

setsockopt(socketfd,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

//发送缓冲区

int nSendBuf=20*1024;//设置为20K

setsockopt(socketfd,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

3.5 设置收发时限

在发送和接收过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:

int nNetTimeout=1000;  //1秒

//发送时限

setsockopt(socketfd,SOL_SOCKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));

//接收时限

setsockopt(socketfd,SOL_SOCKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

3.6 允许套接字绑定已使用的端口

有时候将服务器关闭之后,端口的释放需要时间,可以设置该数据允许套接字绑定正在被占用的端口。

int on = 1;

setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

3.7 忽略SIGPIPE信号

往一个已经接收到FIN的套接中写是允许的,接收到的FIN仅仅代表对方不再发送数据。并不能代表我不能发送数据给对方。

往一个FIN结束的进程中写(write),对方会发送一个RST字段过来,TCP重置。

如果再调用write就会产生SIGPIPE信号。

(也就是当服务器向客户端发送数据时,客户端突然断开连接,会导致SIGPIPE信号产生,如果不处理,系统默认的处理方式就终止进程)

signal(SIGPIPE,SIG_IGN);  //忽略SIGPIPE信号

3.8 获取网络底层缓冲区发送剩余字节数

在网络编程时,发送方调用write(fd)将报文发送的时候实际上只是写入了内核的write buffer。接收方什么时候能收到报文是个未知数。

在某些需要同步状态机的地方,发送方最好能够确认接收方收到报文后再进行下一步动作。

linux提供了ioctl(fd, SIOCOUTQ, &count)方法来查询一个tcp socket的write buffer是否清空。发送方一般可以用这个方法来判断对端是否收到报文。当底层网卡将缓冲区的数据全部发送成功时,获取的count=0

#include 

#include 

int value;

ioctl(client_fd,SIOCOUTQ,&value);

3.9 获取当前网络协议底层发送与接收缓冲区大小

int sockfd;

/*1. 创建socket套接字*/

sockfd=socket(AF_INET,SOCK_STREAM,0);

int nRecvBuf;

socklen_t  len=4;

getsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&nRecvBuf,&len);

printf("接收缓冲区大小=%d\n",nRecvBuf);

//发送缓冲区

int nSendBuf;

getsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,&nSendBuf,&len);

printf("发送缓冲区大小=%d\n",nSendBuf);

Redhat6.3系统上输出结果:

接收缓冲区大小=87380

发送缓冲区大小=16384

Linux 数据结构 网络

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:excel表格筛选两表间重复数据的教程(excel表两个表格怎么筛选重复数据)
下一篇:带你彻底搞懂Git上的远程分支与变基【云驻计划】(git 覆盖远程分支)
相关文章