0%

Linux网络通信编程简记

记录一些Linux下网络通信编程的笔记。

常用的socket类型

流式套接字(SOCK_STREAM)

提供可靠的、面向连接的通信流,使用TCP协议,从而保证了数据传输的可靠性和顺序性。

数据报套接字(SOCK_DGRAM)

定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的,使用UDP协议。

原始套接字(SOCK_RAW)

允许对底层协议如IP或ICMP进行直接访问,主要用于一些协议的开发。

sockaddr & sockaddr_in

sockaddrsockaddr_in是两个用于存储网络套接字的结构体。

sockaddr定义

头文件<sys/socket.h>

struct sockaddr
{
unsigned short sa_family; //地址族,AF_INET表示使用IPv4协议;AF_INET6表示使用IPv6协议
char sa_data[14]; //协议地址,包含该socket的IP地址和端口号
};

sockaddr_in定义

头文件<netinet/in.h>

struct sockaddr_in
{
short int sa_family; //地址族
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //IP地址
unsigned char sin_zero[8]; //填充0以保持与sockaddr同样大小
};

sockaddr_in中的in_addr其实就是一个整数:

struct in_addr
{
unsigned long s_addr;
};

IP地址格式转换

inet_addr()函数

  • 头文件#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>

  • 定义函数in_addr_t inet_addr(const char *cp);

  • 函数说明inet_addr()用来将参数*cp所指的网络地址字符串转换成二进制数值

  • 返回值:成功则返回对应的二进制数值,失败返回-1

255.255.255.255现在也是一个有效的地址,不过inet_addr()无法处理,函数返回-1

inet_aton()函数

  • 头文件#include <sys/scoket.h>#include <netinet/in.h>#include <arpa/inet.h>
  • 定义函数int inet_aton(const char * cp, struct in_addr *inp);
  • 函数说明inet_aton()用来将参数*cp所指的网络地址字符串转换成二进制数值,然后存于参数*inp所指的in_addr结构体
  • 返回值:成功则返回非0值,失败则返回0

inet_ntoa()函数

  • 头文件#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>
  • 定义函数char * inet_ntoa(struct in_addr in);
  • 函数说明inet_ntoa()用来将参数in所指的二进制数值转换成网络地址,然后将指向此网络地址字符串的指针返回
  • 返回值:成功则返回字符串指针,失败则返回NULL

举个栗子

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(void)
{
// 输出IP地址192.168.31.1的16进制值
char* ip = "192.168.31.1";
struct in_addr inp;
inet_aton(ip, &inp);
printf("%s --> 0x%X\n", ip, inp);

// 输出16进制值0x11FA8C0对应的IP地址
u_int32_t addr = 0x11FA8C0;
inp.s_addr = addr;
printf("0x%X --> %s\n", addr, inet_ntoa(inp));

return 0;
}

运行结果

查看本机IP地址

getifaddrs()函数

int getifaddrs(struct ifaddrs *ifap);获取本地网络接口的信息。

结构体ifaddrs定义

struct ifaddrs
{
struct ifaddrs *ifa_next; //指向链表的下一个成员
char *ifa_name; //接口名
unsigned int ifa_flags; //接口的标识位
struct sockaddr *ifa_addr; //接口地址
struct sockaddr *ifa_netmask; //接口的子网掩码
union
{
struct sockaddr *ifu_broadaddr; //广播地址接口
struct sockaddr *ifu_dstaddr; //点对点目的地址
} ifa_ifu;
void *ifa_data; //地址特定的数据
};

举个栗子

#include <stdio.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <arpa/inet.h>

int main (int argc, const char * argv[])
{
struct ifaddrs * ifAddrStruct = NULL;
struct sockaddr_in * sin = NULL;

getifaddrs(&ifAddrStruct);
while (ifAddrStruct != NULL)
{
if (ifAddrStruct->ifa_addr->sa_family == AF_INET) // 只获取IPv4地址
{
sin = (struct sockaddr_in *)ifAddrStruct->ifa_addr;
printf("%s IP Address %s\n", ifAddrStruct->ifa_name, inet_ntoa(sin->sin_addr));
}
ifAddrStruct = ifAddrStruct->ifa_next;
}
return 0;
}

运行结果

套接字相关API

socket()函数

  • 头文件#include <sys/types.h>#include <sys/socket.h>

  • 定义函数int socket(int domain, int type, int protocol);

  • 函数说明socket()用来建立一个新的socket,也就是向系统注册,通知系统建立一通信端口

    • domain:协议族,一般为AF_INET
    • type:套接字类型
    • protocol0(原始套接字除外)
  • 返回值:成功则返回socket处理代码,失败则返回-1

bind()函数

  • 头文件#include <sys/types.h>#include <sys/socket.h>

  • 定义函数int bind(int sockfd, struct sockaddr * my_addr, int addrlen);

  • 函数说明bind()函数用来将一个套接字与一个地址相关联

    • sockfd:套接字描述符
    • * my_addr:本地地址
    • addrlen:地址长度
  • 返回值:成功则返回0,失败则返回-1,错误原因存于errno

connect()函数

  • 头文件#include <sys/types.h>#include <sys/socket.h>

  • 定义函数int connect(int sockfd, struct sockaddr * serv_addr, int addrlen);

  • 函数说明connect()用来将参数sockfd的socket连接到参数serv_addr指定的网络地址;一个套接字只能连接一次,如果客户端要和其他服务端通信,必须再创建一个流式套接字,重新连接

  • 返回值:成功则返回0,失败则返回-1,错误原因存于errno

listen()函数

  • 头文件#include <sys/socket.h>

  • 定义函数int listen(int sockfd, int backlog);

  • 函数说明:监听来自客户端TCP的连接请求;通常listen()会在socket()bind()之后调用,接着才调用accept()listen()只适用SOCK_STREAMSOCK_SEQPACKET的socket类型,如果socket为AF_INET则参数backlog最大值可设至128

    • sockfd:套接字描述符
    • backlog:请求队列中允许最大请求数
  • 返回值:成功则返回0,失败则返回-1,错误原因存于errno

accept()函数

  • 头文件#include <sys/types.h>#include <sys/socket.h>

  • 定义函数int accept(int sockfd, struct sockaddr * addr, int * addrlen);

  • 函数说明:服务端套接字在进入监听状态后,必须通过调用accept()接收客户端提交的连接请求,才能完成一个套接字的完整连接;accept()一旦调用成功,系统将创建一个属性与套接字sockfd相同的新的套接字描述符,用于与客户端通信,并返回该新套接字的标识符,而原套接字sockfd仍然用于监听

    • sockfd:套接字描述符
    • * addr:客户端地址,若不关心客户端套接字的地址信息,可设置为NULL
    • * addrlen:地址长度,即客户端地址结构占用的字节空间大小,若不关心客户端套接字的地址信息,可设置为NULL
  • 返回值:成功则返回新的socket处理代码,失败则返回-1,错误原因存于errno

send()函数

  • 头文件#include <sys/types.h>#include <sys/socket.h>

  • 定义函数int send(int sockfd, const void * msg, int len, unsigned int flags);

  • 函数说明send()用来将数据由指定的socket传给对方主机

    • sockfd:套接字描述符
    • * msg:指向要发送数据的指针
    • len:数据长度
    • flags:一般为0
  • 返回值:成功则返回实际发送的字符数,失败则返回-1,错误原因存于errno

recv()函数

  • 头文件#include <sys/types.h>#include <sys/socket.h>

  • 定义函数int recv(int sockfd, void * buf, int len, unsigned int flags)

  • 函数说明recv()用来接收其它主机经指定的socket发送的数据

    • sockfd:套接字描述符
    • * buf:存放已接收数据的缓冲区
    • len:数据长度
    • flags:一般为0
  • 返回值:成功则返回接收到的字符数,失败则返回-1,错误原因存于errno

sendto()函数

  • 头文件#include <sys/types.h>#include <sys/socket.h>

  • 定义函数int sendto(int sockfd, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen);

  • 函数说明sendto()用来将数据由指定的socket发送给对方主机(UDP)

    • sockfd:套接字描述符
    • * msg:指向要发送数据的指针
    • len:数据长度
    • flags:一般为0
    • * to:目地主机的IP地址和端口号
    • tolen:地址长度
  • 返回值:成功则返回实际发送的字符数,失败则返回-1,错误原因存于errno

recvfrom()函数

  • 头文件#include <sys/types.h>#include <sys/socket.h>

  • 定义函数int recvfrom(int sockfd, void * buf, int len, unsigned int flags, struct sockaddr * from,int * fromlen);

  • 函数说明recv()用来接收其它主机经指定的socket发送的数据(UDP)

    • sockfd:套接字描述符
    • * buf:存放已接收数据的缓冲区
    • len:数据长度
    • flags:一般为0
    • * from:源主机的IP地址和端口号
    • * fromlen:地址长度
  • 返回值:成功则返回接收到的字符数,失败则返回-1,错误原因存于errno

TCP socket通信例程

服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXLINE 4096
#define PORT 8000

int main(void)
{
int listen_fd = -1; //服务端监听套接字
int connect_fd = -1; //服务端连接套接字
struct sockaddr_in servaddr; //服务端对应的套接字地址

char sendbuf[MAXLINE]; //发送数据的缓冲区
char recvbuf[MAXLINE]; //接收数据的缓冲区

memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET; //IPv4协议
servaddr.sin_port = htons(PORT); //设置监听端口
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //接收任意IP的连接请求

if((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) //创建套接字
{
printf("create socket error: %s(error: %d)\n", strerror(errno), errno);
exit(0);
}

if(bind(listen_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) //将套接字与服务端IP地址和端口号关联
{
printf("bind socket error: %s(error: %d)\n", strerror(errno), errno);
exit(0);
}

if(listen(listen_fd, 10) == -1) //监听来自客户端TCP的连接请求
{
printf("listen socket error: %s(error: %d)\n", strerror(errno), errno);
exit(0);
}
printf("等待客户端发起连接……\n");

while(1)
{
if((connect_fd = accept(listen_fd, (struct sockaddr*)NULL, NULL)) == -1) //接收客户端提交的连接请求
{
printf("accept socket error: %s(error: %d)\n", strerror(errno), errno);
continue;
}

while(1)
{
//读取来自客户端的信息
ssize_t len = read(connect_fd, recvbuf, sizeof(recvbuf));
if(len < 0)
{
if(errno == EINTR)
{
continue;
}
exit(0);
}
printf("接收客户端的信息:%s\n", recvbuf);

printf("回复客户端的信息:");
fgets(sendbuf, sizeof(sendbuf), stdin);
write(connect_fd, sendbuf, sizeof(sendbuf));
}

close(connect_fd);
}

close(listen_fd);
}

客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define MAXLINE 4096
#define PORT 8000

int main(void)
{
int sockfd = -1; //客户端套接字
struct sockaddr_in servaddr; //服务端套接字地址

char sendbuf[MAXLINE]; //发送数据的缓冲区
char recvbuf[MAXLINE]; //接收数据的缓冲区

memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET; //IPv4协议
servaddr.sin_port = htons(PORT); //服务端的端口号
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //服务端的IP地址

if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) //创建套接字
{
printf("create socket error: %s(error: %d)\n", strerror(errno), errno);
exit(0);
}

if(connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) //向服务端发送连接请求
{
printf("connect socket error: %s(error: %d)\n", strerror(errno), errno);
exit(0);
}

while(1)
{
printf("向服务端发送信息:");
fgets(sendbuf, sizeof(sendbuf), stdin);
write(sockfd, sendbuf, sizeof(sendbuf));

//从服务端接收信息
ssize_t len = read(sockfd, recvbuf, sizeof(recvbuf));
if(len < 0)
{
if(errno == EINTR)
{
continue;
}
exit(0);
}
printf("服务端的应答信息:%s\n", recvbuf);
}

close(sockfd);
}

运行结果


参考资料

C语言接口处理函数

linux网络编程之TCP/IP的TCP socket通信过程(含实例代码)

c++网络编程中的inet_ntoa()函数与inet_aton()函数介绍

inet_aton、inet_addr和inet_ntoa