#导入Word文档图片# Linux下网络编程(socket)
Posted DS小龙哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#导入Word文档图片# Linux下网络编程(socket)相关的知识,希望对你有一定的参考价值。
第一章 TCP网络编程
socket创建套接字
#include <sys/types.h> #include <sys/socket.h> 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即可!
图1-1
返回值
成功返回网络套接字,与open函数返回值类似。
示例
Clientfd = socket(PF_INET,SOCK_STREAM,0); |
bind绑定IP-端口
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> 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表示失败!
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 sa_family_t sa_family; //网络协议版本。填写:AF_INET 或者 AF_INET6。 char sa_data[14]; //IP地址和端口
|
- struct sockaddr_in结构体成员:查看IPV4协议帮助文档:# man 7 ip
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 存放IP地址的结构体*/ ; /* Internet address. */ struct in_addr uint32_t s_addr; /* address in network byte order IP地址 */ ; |
1.3.2 端口号赋值
计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。 Internet 上数据以高位字节优先顺序在网络上传输, 所以对于在内部是以低位字节优先方式存储数据的机器, 在 Internet 上传输数据时就需要进行转换, 否则就会出现数据不一致。
普通人用的桌面电脑,只要是Intel或AMD的x86/x64架构就一定是小端字节序。
外很多ARM CPU可以选择数据指令字节序,不过通常也都是运行小端字节序(比如我们的智能手机)。
网络设备,像PowerPC核心的一些路由器,默认运行大端字节序。
下面是几个字节顺序转换函数:
·htonl(): 把 32 位值从主机字节序转换成网络字节序
·htons(): 把 16 位值从主机字节序转换成网络字节序
·ntohl(): 把 32 位值从网络字节序转换成主机字节序
·ntohs(): 把 16 位值从网络字节序转换成主机字节序
函数原型
#include <arpa/inet.h> 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 本地计算机大小端判断
首先说明,电脑大小端指的是一种存储模式。
1.为什么有大小端:
在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题,因此就导致了大端存储模式和小端存储模式。
2.大小端定义:
大端模式(Big-endian),是指数据的高字节,保存在内存的低地址中,而数据的低字节,保存在内存的高地址中。
小端模式(Little-endian),是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。
3.直接来看一个图,详细说明大小端:
例子:int i = 0x12345678 两种模式存入内存:
4. 判断大小端的C语言代码
#include<stdio.h> int CheckSystem() unioncheck 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开始的地址是一样的读的话还是从低字节向高字节完整的读取 |
listen监听端口的数量
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog); |
功能
设置服务器需要监听的端口数量。决定了能够连接的服务器数量。
返回值
成功返回0,失败返回-1。
服务器创建,函数调用顺序:
图1-2
- 示例:listen(Serverfd,10);
accept 等待客户端连接
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> 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); |
connect连接服务器
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> 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)); |
send/ recv网络数据收发
#include <sys/types.h> #include <sys/socket.h> 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函数替换。
shutdown关闭连接
#include <sys/socket.h> 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 AddressForeign Address State PID/Program name tcp 0 0 192.168.61.129:40985203.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.1 recvfrom函数
UDP使用recvfrom()函数接收数据,他类似于标准的read(),但是在recvfrom()函数中要指明数据的目的地址。
#include <sys/types.h> #include <sys/socket.h> 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)
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 <sys/types.h> #include <sys/socket.h> 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]) 以上是关于#导入Word文档图片# Linux下网络编程(socket)的主要内容,如果未能解决你的问题,请参考以下文章 #导入Word文档图片# Linux下Shell脚本语言编程 |