Linux socket网络编程(一)(TCP编程实例、socket编程函数、套接字类型和网络模型)

一、OSI网络模型和TCP/UDP/IP协议计算机网络编程必定少不了网络技术,目前标准的网络模型为ISO七层网络模型,分别为物理层、数据链路层、网络层、传输层、会话层、表现层、应用层,目前实现的是五层模型,更多可参考网络模型的原理和作用详细解释。这里侧重解析一下网络层和传输层,在网络模型中,每一层都有不同的协议,网络层的协议主要有IP互联网协议(Internet Protocol),该协议负责切分数据包,在IP协议中我们需要关注的有:源地址(Source address)和目标地址(Destination adress),这两个值就是我们常说的IP,源地址就是本机地址,目标地址是需要连接主机的地址。
就IP地址的类型来说,常见的有三类:A类地址,B类地址,C类地址,IPv4实质是用32位二进制表示,平时看到的都是十进制表示,如192.168.1.,34。

Linux socket网络编程(一)(TCP编程实例、socket编程函数、套接字类型和网络模型)

文章图片
IP地址有网络段和主机端两部分,主要根据这两部分位数的不同而分类,如C类地址的网络段有24位,主机段有8位,一般我们局域网内的私有地址都是C类地址。
Linux socket网络编程(一)(TCP编程实例、socket编程函数、套接字类型和网络模型)

文章图片
网络层IP协议根据IP地址决定数据的传输方向,根据网络段判断源地址和目标地址是否在同一个网段,根据主机段找到确切的主机。一个IP头的详细信息如下:
Linux socket网络编程(一)(TCP编程实例、socket编程函数、套接字类型和网络模型)

文章图片
传输层的功能是决定数据的传输方式,主要的协议有TCP协议和UDP协议,TCP协议面向连接,需要提前确定连接的可靠性才建立TCP连接,UDP协议面向非连接,不需要确保连接的可靠性。
TCP/UDP协议的主要内容是:源端口(Source Port)和目标端口(Destination Port),源端口即本地应用的端口,目标端口为传输目的主机的端口。端口一般是应用层的应用程序提供,常见的有FTP文件服务21端口,HTTP服务默认80端口等,端口的作用是标明应用程序。一个TCP协议的详细报头信息如下:
Linux socket网络编程(一)(TCP编程实例、socket编程函数、套接字类型和网络模型)

文章图片
UDP报头类似,但是信息更加简化。
所以,网络模型、TCP/UDP/IP协议和Socket网络编程有什么关系呢?我们使用socket开发的程序都属于应用层,网络编程至少需要提供网络层和传输层需要的信息,从上面你可以看到,在网络层需要提供IP协议中的本机和目标主机的IP地址,在传输层需要提供本机和目标主机的端口。顺便提一下,一般来说端口是应用程序提供的,但是由于有NAT的存在,如果两台主机不在同一个网段,被访问的主机的端口就不是应用程序的端口,而是NAT映射后的端口,IP也是映射后的IP地址。
二、socket套接字和相关信息的数据结构套接字即socket,Linux网络编程主要使用socket接口,网络编程的实质是实现进程间的通信,只不过多数情况下,两个进程不在同一台主机或者同一个网段。套接字是一个数据结构,主要由网络层和传输层的相关信息组成。网络编程实际使用是一种通信资源,一种文件,但是我们在socket网络编程中并没有真正持有通信对象,没有socket,使用的是socket文件描述符,一个int整形数,这多少会令人费解,建议了解一下关于文件和文件描述符的原理分析。
1、网络层协议类型和socket连接类型
编程中主要使用socket描述符进行网络通信,创建socket首先需要确定网络层和传输层的协议类型,具体如下:
网络层常见协议类型(地址类型)
AF_LOCAL:进程通信协议;
AF_INET:IPv4网络协议;
AF_INET6:IPv6网络协议;
AF_IPX:IPX-Novell协议。
传输层的socket连接类型
SOCK_STREAM:提供TCP可靠的数据传输;
OOB机制:数据发送前必须使用connect()建立连接状态;
SOCK_DGRAM:提供不可靠的数据传输,即UDP用户数据报传输;
SOCK_RAW:原始网络协议存取。
2、socket信息数据结构
使用一个结构体封装socket连接必要的信息,主要使用的结构体有:
struct sockaddr{ unsigned short sa_family; // 地址协议类型 char sa_data[14]; // 14字节协议地址,包括IP地址和端口号 }struct sockaddr_in{ short int sin_family; // 地址协议类型 unsigned short int sin_port; // 端口号 struct in_addr sin_addr; // IP地址结构体 unsigned char sin_zero[8]; // 填充0以兼容使用struct sockaddr_in*0 }struct in_addr{ unsigned long s_addr; // IP地址 }

编程中我们使用的是struct sockaddr_in,但是函数接口是struct sockaddr,所以我们需要使用使用bzero或memset将结构体清零,但是这样编译还是有警告信息,处理警告信息可以使用类型强制转换。
3、端口和IP处理
计算机和网络存储数据的排列方式是不同的,上面我们看到的结构体struct sockaddr_in中的数据都会被封装到数据包中传输到网络,所以需要将该结构体中数据的字节顺序进行转换,但是sin_family不会传输到网络,该属性只是表明使用的地址协议类型,我们只需处理sin_port和sin_addr。
(1)首先是处理端口。本人对C的变量命名都是很困惑,有些变量缩短意思都好难猜,这里涉及要处理的函数,主要就是主机和网络的字节顺序转换,主机为s:host,网络为n:network,是to,另外有两种转换的数据类型,s:short,l:long。
端口处理一共有4个函数:htons(),htonl(),ntohs(),ntohl()。
(2)接着处理32位二进制IP地址。从上面我们可以看到IP地址s_addr是长整型的,但是我们一般使用的都是字符串形式的点分十进制,这是32位二进制和16位点分十进制之间的转换。
32位二进制转点分十进制IP地址:inet_ntoa()
点分十进制转32位二进制IP地址:inet_aton()、inet_addr();
兼容IPv4和IPv6的函数有:inet_pton()、inet_ntop()。
(3)域名和IP地址之间的转换。
为了方便我们通常会使用域名,但是实际网络数据传输使用的是IP地址,这里也需要进行转换,以下函数头文件为netdb.h
域名转IP地址:gethostbyname();
IP地址转为域名或主机名:gethostbyaddr()。
这两个函数的返回值是一个结构体,声明如下:
struct hostent{ char *h_name; //主机名 char **h_aliases; // 主机别名 int h_addrtype; // 主机IP地址协议类型 int h_length; // 主机IP地址字节长度 char **h_addr_list; // 主机IP地址列表 }

三、socket网络编程函数详解1、socket()函数
函数定义:int socket(int domain, int type, int protocol);
domain为地址协议类型,AF_INET为IPv4,AF_INET6为IPv6;
type为socket传输类型,SOCK_STREAM为TCP,SOCK_DGRAM为UDP传输;
protocol为传输协议的编号,一般为0。
该函数创建一个socket通信,返回socket文件描述符,失败返回-1。
2、bind()函数
函数定义:int bind(int sockfd, struct sockaddr *addr, int addr_len);
sockfd为socket文件描述符;
addr为socket信息结构体,一般使用struct sockaddr_in*;
addr_len为socket信息结构体的长度。
该函数将socket通信sockfd和socket通信信息addr绑定起来,失败返回-1。
3、listen()函数
函数定义:int listen(int s, int backlog);
s为socket文件描述符;
backlog指同时能连接的最大请求。
该函数用于监听端口,等待连接。
4、accept()函数
函数定义:accept(int s, struct sockaddr *addr, int *addrlen);
s为socket文件描述符;
addr为socket信息结构体;
addrlen为addr的长度。
该函数用于接受socket连接,成功返回新的socket,可使用该socket进行后续处理。
5、connet()函数
函数定义:int connet(int sockfd, struct sockaddr *serv_addr, int addrlen);
sockfd为socket文件描述符;
serv_addr为服务器的连接信息结构体;
addrlen为serv_addr的长度。
该函数用于建立socket连接。
6、send()函数
函数定义:int send(int s, const void *msg, int len, unsigned int flags);
s为socket文件描述符;
msg为需要发送的信息;
len为信息的长度;
flags一般设置为0即可。
该函数用户发送数据。
7、recv()函数
函数定义:int recv(int s, void *msg, int len, unsigned int flags);
s为socket文件描述符;
msg为需要接收的信息;
len为信息的长度;
flags一般设置为0即可。
该函数用于接收数据。
四、socket编程实例TCP服务端创建连接顺序:socket,bind,listen,accept,recv,send,close。
客户端的连接顺序:socket,connect,send,recv,close。
TCP服务端实例代码,单长连接,下节再处理多客户端的长连接:
#include < stdio.h> #include < stdlib.h> #include < string.h>#include < time.h>#include < sys/types.h> #include < sys/socket.h> #include < netinet/in.h> #include < arpa/inet.h>int main(int argc, char *argv[]){ int socket_s = socket(AF_INET, SOCK_STREAM, 0); if(socket_s < 0){ perror("server socket init"); return; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8989); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); int addr_len = sizeof(struct sockaddr_in); if(bind(socket_s, (struct sockaddr *)& addr, addr_len)){ perror("server bind"); return; } printf("start listening...\n"); if(listen(socket_s, 4) < 0){ perror("server listen"); return; } printf("end listen...\n"); int socket_re = accept(socket_s, (struct sockaddr *)& addr, & addr_len); char buffer[256]; int i; while(1){ struct sockaddr_in client_addr; int caddr_len = sizeof(struct sockaddr_in); printf("start accepting...\n"); //int socket_re = accept(socket_s, (struct sockaddr *)& client_addr, & caddr_len); memset(buffer, 0, sizeof(buffer)); printf("start recving...\n"); recv(socket_re, & buffer, sizeof(buffer), 0); int seconds = time(NULL); printf("%dmessage\t%s\n", seconds, buffer); char response[256] = "server response"; send(socket_re, & response, sizeof(response), 0); //close(socket_re); } close(socket_s); return 0; }

【Linux socket网络编程(一)(TCP编程实例、socket编程函数、套接字类型和网络模型)】客户端实例代码:
#include < stdio.h> #include < stdlib.h> #include < string.h>#include < time.h>#include < sys/socket.h> #include < sys/types.h> #include < netinet/in.h>int main(int argc, char *argv){ int socket_s = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addr; bzero(& addr, 0); addr.sin_family = AF_INET; addr.sin_port = htons(8989); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); connect(socket_s, (struct sockaddr *)& addr, sizeof(addr)); char buffer[256]; char message[256]; while(1){ memset(buffer, 0, sizeof(buffer)); memset(message, 0, sizeof(message)); read(0, message, sizeof(message)); send(socket_s, & message, sizeof(message), 0); recv(socket_s, buffer, sizeof(buffer), 0); int seconds = time(NULL); printf("%dmessage\t%s\n", seconds, buffer); } }

    推荐阅读