丈夫欲遂平生志,一载寒窗一举汤。这篇文章主要讲述#导入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);
|
#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地址 */ ; |
计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。 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); 网际协议在处理这些多字节整数时,使用大端字节序。 在主机本身就使用大端字节序时,这些函数通常被定义为空宏。 |
示例:
/*结构体成员赋值*/ 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地址赋值 |
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);
|
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开始的地址是一样的读的话还是从低字节向高字节完整的读取 |
#include <
sys/types.h>
/* See NOTES */ #include < sys/socket.h> int listen(int sockfd, int backlog); |
设置服务器需要监听的端口数量。决定了能够连接的服务器数量。
返回值
成功返回0,失败返回-1。
服务器创建,函数调用顺序:
图1-2
- 示例:listen(Serverfd,10);
#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); |
#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));
|
#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函数替换。
#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);
|
查看示例:
[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 |
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:1314ESTABLISHED 20955/./app_c |
连接类型: TCP协议
本地IP地址和端口号: 192.168.61.129:40985?
与其通信的远程IP地址和端口号: 203.195.170.73:1314?
状态: ESTABLISHED(已建立的连接)?
进程PID号与应用程序名称: 20955/./app_c?
- socket网络连接的状态如下
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)); |
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); //端口 【#导入Word文档图片# Linux下网络编程(socket)】 ServerSocket.sin_addr.s_addr=inet_addr(argv[1]) 推荐阅读
|