Linux-C网络编程
简介
基础是TCP/IP协议,网上资料很多不再赘述。
推荐《图解TCP/IP》
socket编程
网络字节序
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存
因此,网络数据流的地址规定:先发出的数据是低地址,后发出的数据是高地址
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
例如UDP段格式,地址0-1是16位的源端口号,如果这个端口号是1000(0x3e8)
则地址0是0x03,地址1是0xe8,也就是先发0x03,再发0xe8
这16位在发送主机的缓冲区中也应该是低地址存0x03,高地址存0xe8。
但是,如果发送主机是小端字节序的,这16位被解释成0xe803,而不是1000。
因此,发送主机把1000填到发送缓冲区之前需要做字节序的转换。
同样地,接收主机如果是小端字节序的,接到16位的源端口号也要做字节序的转换。
如果主机是大端字节序的,发送和接收都不需要做转换。
同理,32位的IP地址也要考虑网络字节序和主机字节序的问题。
网络字节序转换
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行
可以调用以下库函数做网络字节序和主机字节序的转换
#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);
h表示host,n表示network,l表示32位长整数,s表示16位短整数;
htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回;
IP地址格式转换
通常用户在表达地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制Ipv6地址)
而在通常使用的socket编程中使用的则是32位的网络字节序的二进制值,这就需要将这两个数值进行转换。
这里在 Ipv4 中用到的函数有inet_aton()
、inet_addr()
和inet_ntoa()
而 IPV4 和 Ipv6 兼容的函数有inet_pton()
和inet_ntop()
。
IPv4的函数原型:
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int inet_aton(const char *straddr, struct in_addr *addrptr); char *inet_ntoa(struct in_addr inaddr); in_addr_t inet_addr(const char *straddr);
-
例子:函数inet_aton():将点分十进制数的IP地址转换成为网络字节序的32位二进制数值- 返回值:成功,则返回1,不成功返回0
- 参数straddr:存放输入的点分十进制数IP地址字符串
- 参数addrptr:传出参数,保存网络字节序的32位二进制数值
-
函数inet_ntoa():将网络字节序的32位二进制数值转换为点分十进制的IP地址
- 函数inet_addr():功能与inet_aton相同,但是结果传递的方式不同。
- inet_addr()若成功则返回32位二进制的网络字节序地址
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main() { char ip[] = "192.168.0.101"; struct in_addr myaddr; /* inet_aton 32字节序*/ int iRet = inet_aton(ip, &myaddr); printf("%x\n", myaddr.s_addr); /* inet_addr 32字节序*/ printf("%x\n", inet_addr(ip)); /* inet_pton 32字节序*/ iRet = inet_pton(AF_INET, ip, &myaddr); printf("%x\n", myaddr.s_addr); myaddr.s_addr = 0xac100ac4; /* inet_ntoa 输出点分十进制IP*/ printf("%s\n", inet_ntoa(myaddr)); /* inet_ntop 输出点分十进制IP*/ inet_ntop(AF_INET, &myaddr, ip, 16); puts(ip); return 0; }
Linux中,gethostbyname()是将主机名转化为IP地址,gethostbyaddr()则是逆操作,是将IP地址转化为主机名。
socket地址的数据类型及相关函
socket API是一层抽象的网络编程接口,适用于各种底层网络协议
如IPv4、IPv6,UNIX Domain Socket。
基于TCP协议的网络程序
TCP程序通信流程图
服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,
处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,
服务器应答一个SYN-ACK段,客户端收到后从connect()返回
同时应答一个ACK段,服务器收到后从accept()返回
TCP程序实例
/*server.c*/ #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #define MYPORT 8887 #define QUEUE 20 #define BUFFER_SIZE 1024 int main() { ///定义sockfd int server_sockfd = socket(AF_INET,SOCK_STREAM, 0); //定义sockaddr_in struct sockaddr_in server_sockaddr; server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(MYPORT); server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); ///bind,成功返回0,出错返回-1 if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1) { perror("bind"); exit(1); } printf("监听%d端口\n",MYPORT); ///listen,成功返回0,出错返回-1 if(listen(server_sockfd,QUEUE) == -1) { perror("listen"); exit(1); } ///客户端套接字 char buffer[BUFFER_SIZE]; struct sockaddr_in client_addr; socklen_t length = sizeof(client_addr); printf("等待客户端连接\n"); ///成功返回非负描述字,出错返回-1 int conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length); if(conn<0) { perror("connect"); exit(1); } printf("客户端成功连接\n"); while(1) { memset(buffer,0,sizeof(buffer)); int len = recv(conn, buffer, sizeof(buffer),0); //客户端发送exit或者异常结束时,退出 if(strcmp(buffer,"exit\n")==0 || len<=0) break; printf("来自客户端数据:%s\n",buffer); send(conn, buffer, len, 0); printf("发送给客户端数据:%s\n",buffer); } close(conn); close(server_sockfd); return 0; }
基于UDP的网络程序
/*client.c*/ #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #define MYPORT 8887 #define BUFFER_SIZE 1024 char* SERVER_IP = "127.0.0.1"; int main() { ///定义sockfd int sock_cli = socket(AF_INET,SOCK_STREAM, 0); ///定义sockaddr_in struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(MYPORT); ///服务器端口 servaddr.sin_addr.s_addr = inet_addr(SERVER_IP); ///服务器ip printf("连接%s:%d\n",SERVER_IP,MYPORT); ///连接服务器,成功返回0,错误返回-1 if (connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("connect"); exit(1); } printf("服务器连接成功\n"); char sendbuf[BUFFER_SIZE]; char recvbuf[BUFFER_SIZE]; while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) { printf("向服务器发送数据:%s\n",sendbuf); send(sock_cli, sendbuf, strlen(sendbuf),0); ///发送 if(strcmp(sendbuf,"exit\n")==0) break; recv(sock_cli, recvbuf, sizeof(recvbuf),0); ///接收 printf("从服务器接收数据:%s\n",recvbuf); memset(sendbuf, 0, sizeof(sendbuf)); memset(recvbuf, 0, sizeof(recvbuf)); } close(sock_cli); return 0; }
UDP通信流程图
UDP例程
/*server.c*/ #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<errno.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<string.h> #define MYPORT 8887 #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0) void echo_ser(int sock) { char recvbuf[1024] = {0}; struct sockaddr_in peeraddr; socklen_t peerlen; int n; while (1) { peerlen = sizeof(peeraddr); memset(recvbuf, 0, sizeof(recvbuf)); n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&peeraddr, &peerlen); if (n <= 0) { if (errno == EINTR) continue; ERR_EXIT("recvfrom error"); } else if(n > 0) { printf("接收到的数据:%s\n",recvbuf); sendto(sock, recvbuf, n, 0, (struct sockaddr *)&peeraddr, peerlen); printf("回送的数据:%s\n",recvbuf); } } close(sock); } int main(void) { int sock; if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) ERR_EXIT("socket error"); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(MYPORT); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); printf("监听%d端口\n",MYPORT); if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("bind error"); echo_ser(sock); return 0; }
/*client.c*/ #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define MYPORT 8887 char* SERVERIP = "127.0.0.1"; #define ERR_EXIT(m) \ do{ \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) void echo_cli(int sock) { struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(MYPORT); servaddr.sin_addr.s_addr = inet_addr(SERVERIP); int ret; char sendbuf[1024] = {0}; char recvbuf[1024] = {0}; while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) { printf("向服务器发送:%s\n",sendbuf); sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL); if (ret == -1) { if (errno == EINTR) continue; ERR_EXIT("recvfrom"); } printf("从服务器接收:%s\n",recvbuf); memset(sendbuf, 0, sizeof(sendbuf)); memset(recvbuf, 0, sizeof(recvbuf)); } close(sock); } int main(void) { int sock; if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) ERR_EXIT("socket"); echo_cli(sock); return 0; }