Linux网络编程常用api

  • Post author:
  • Post category:linux

总览

在这里插入图片描述

大端小端

大端字节序: 又称为网络字节序。大端字节序一般指:一个整数的高位字节放在地址的低位地址上。(Java也是大端)(大端法与一般人类的写法习惯相同)
小端字节序: 又称主机字节序。 一般指整数的高位字节位于地址的高位地址上。

Cpp中一般可以使用union判断大端小端,利用了union的特性:从低位地址向高位地址延伸。

大小端检测

union{
	short val;
	char bytes[sizeof(short)];
} test;

void check(){
	test.val = 0x0102;
	if(test.bytes[0] == 1 && test.bytes[1] == 2){
		// 大端
	}else if(test.bytes[0] == 2 && test.bytes[1] == 1){
		// 小端
	}else{
		// What the f***?
	}
}

大小端转换

在<netinet/in.h>中提供了转换的函数

#include<netinet/in.h>
// 一般用来转ip
// 将32位的网络字节序(大端)转化位主机字节序(小端)
unsigned long int ntohl(unsigned long int netlong);
// 将32位的主机字节序转为网络字节序
unsigned long int htonl(unsigned long int hostlong);

// 一般用来转port
unsigned short int htons(unsigned short int hostshort);
unsigned short int ntohs(unsigned short int netshort);

socket地址API

通用socket地址

socket网络编程中的地址是结构体sockaddr,在头文件bits/socket.h

struct sockaddr{
	sa_family_t sa_family;
	char sa_data[14];
};

sa_family是地址族类型sa_family_t的变量,常见的协议族(protocol family,又称domain)有

  • PF_UNIX (等价AF_UNIX) , UNIX本地域协议族
  • PF_INET (AF_INET), TCP/IPv4协议族
  • PF_INET6 (AF_INET6), TCP/IPv6协议族
    其中PF_* 与 AF_*等价。

sa_data存放相应的socket地址值

  • PF_UNIX, 文件路径名,可达108字节
  • PF_INET,16bit的端口号,32bit的IPv4地址,共6B
  • PF_INET6, 16bit端口号,32bit流标识,128bit IPv6地址,32bit范围id

在这里插入图片描述

专用socket地址

PF_UNIX专用地址

#include<sys/un.h>
struct sockaddr_un{
	sa_family_t sin_family; // protocol family
	char sun_path[108];// file path name
};

PF_INET

与scokaddr的区别是,没有将端口号和IP地址粘连起来。

#include<netinet/in.h>
struct sockaddr_in{
	sa_family_t sin_family; 
	u_int16_t sin_port; // port 
	struct in_addr sin_addr; // Ipv4 struct
};

strcut in_addr{
	u_int32_t s_addr; // ipv4 address, 用网络字节序(大端)表示
};

PF_INET6

用于IPv6,需要的时候查就可以了,不需要特意记忆
在这里插入图片描述

总结

这些专用结构解决了sockaddr将port与address粘连在一个array的问题。
**Key:**所有的专用socket地址类型的变量在实际使用的时候都需要转化为通用的socket地址类型sockaddr(可以直接强制转化)。因为所有socket接口的地址参数都是默认sockaddr。

IP转换函数

include<arpa/inet.h>
// 将字符串IPv4转为网络字节序的IPv4
in_addr_t inet_addr(const char* strptr);
// 同上,将结果存在inp指针内,成功返回1
int inet_aton(const char* cp, struct in_addr* inp);
// 将网络字节序整数表示的IPv4转化为字符串
char* inet_ntoa(struct in_addr in);

Notice: inet_ntoa内部使用静态变量存储转化结果,因此是不可重入的。

例子

#include<iostream>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
int main(){
    const char* strIP = "1.13.2.13";
    in_addr ip;
    inet_aton(strIP, &ip);
    const char* ipadd = inet_ntoa(ip);
    cout<<"strIP is "<<strIP<<", in_addr_t is "<<ip.s_addr<<", to ip address is "<<ipadd<<endl;
    cout<<"in_addr_t is "<<inet_addr(strIP)<<endl;

    in_addr ip2;
    ip2.s_addr = inet_addr("192.168.0.1");
    const char* ip2add = inet_ntoa(ip2);

    cout<<"ip1 address is "<<ipadd<<endl;
    cout<<"ip2 address is "<<ip2add<<endl;
}
/*
console is:
strIP is 1.13.2.13, in_addr_t is 218238209, to ip address is 1.13.2.13
in_addr_t is 218238209
// 说明了不可重载性
ip1 address is 192.168.0.1
ip2 address is 192.168.0.1
/*

也可以使用inet_pton进行转换
int inet_pton(int af, const char* src, void* dst)

int main(){
    in_addr IP;
    cout<<inet_pton(PF_INET, "192.168.0.0", &IP)<<endl;
    cout<<IP.s_addr<<" "<<inet_addr("192.168.0.0")<<endl;
    cout<<ntohl(inet_addr("192.168.0.0"))<<endl;
}
/*
1
// 默认转换的都是网络序
43200 43200
// 可以使用ntols转换为主机序
3232235520
*/

使用socket

创建socket

socket在Linux中也是一个文件。返回一个文件描述符,失败返回-1
int socket(int domain, int type, int protocol)

domain

使用的协议栈, IPv4——PF_INET, UNIX本地域——PF_UNIX

type

SOCK_STREAM 流服务,对TCP/IP domain来说就是TCP
SOCK_UGRAM 数据报服务,对TCP/IP domain来说就是UDP

protocol

几乎所有的默认协议族默认设为0就ok,特殊情况查文档

命名socket——bind()

将socket绑定到ip和port上
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen)
将my_addr绑定到sockfd的socket上, addrlen指出了该socket地址的长度。bind成功返回0,失败返回-1并设置errno,常见的errno:

  • EACCES:被绑的的地址是受保护地址(0-1023)
  • EADDRINUSE,被绑定地址在使用中。比如绑定到了一个在TIME_WAIT状态的socket地址。

监听socket——listen

创建一个监听队列以存放待处理的客户链接
int listen(int sockfd, int backlog) sockfd指定被监听的socket,backlog指定内核监听队列的最大长度。如果监听队列的长度超过backlog的话,将不受理新的连接。成功返回0,失败-1
Linux2.2之前,backlog指的是SYN_RCVD状态和ESTABLISHED状态的个数,2.2后指的是ESTABLISHED状态的个数。 半连接状态的上限在内核参数中设置。

发起连接——connect

int connect(int sockfd, const struct sockaddr* serv_addr, socklen_t addrlen)
成功时返回0,一旦成功,sockfd就唯一标识了这个连接。客户端就可以通过读写sockfd来与服务器通信。

关闭连接——close

直接使用关闭文件的系统调用来完成。
unistd中的int close(int fd)
调用之后将fd的引用计数减一,当fd引用计数为0之后就真正关闭连接。fork操作会使父进程的fd加一,因此只有父子进程都执行了close才会关闭连接。

强制关闭,不管减一
int shutdown(int fd, int howto)
howto: SHUT_RD, SHUT_WR, SHUTRDWR 分别为关闭读、写,读写。

数据读写

可以使用基于文件的write,read。

TCP数据读写

也有专用的系统调用接口,TCP的是

#include<sys/socket.h>
#include<sys/types.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

buf和len指定缓冲区和大小。flags一般设置为0即可。
recv成功时返回读取到数据的长度,可能小于len,所以有时需要多次调用recv才可以成功收集到数据。返回0代表对方已经关闭了连接。
send是往sockfd写入数据,buf和len指定写缓冲区和大小。成功时返回实际写入数据的长度。
flags:
在这里插入图片描述

UDP数据读写

ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);

ssize_t sendto(int sockfd, const void* buf, int len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);

在这里插入图片描述

Example

最基本的服务端和客户端模型。
注意:如果用的云服务器,一般服务商是默认关闭端口的,要在防火墙手动开启。

Server

#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
#include<signal.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<cstring>
#include<assert.h>
#include<iostream>
const int BUF_SIZE = 1024;

int main() {
	int port = 19980;
	struct sockaddr_in address;
	bzero(&address, sizeof(address));

	address.sin_family = AF_INET;
	address.sin_port = htons(port);
	address.sin_addr.s_addr = htonl(INADDR_ANY);

	int sock = socket(AF_INET, SOCK_STREAM, 0);
	assert(sock > 0);

	int ret = bind(sock, (sockaddr*)(&address), sizeof(address));
	perror("bind");
	assert(ret != -1);
	// 5个最大连接数
	ret = listen(sock, 5);
	perror("bind");
	assert(ret != -1);

	sockaddr_in client;
	socklen_t client_len = sizeof(client);
	printf("Start accept\n");
	int connfd = accept(sock, (sockaddr*)(&client), &client_len);
	printf("Accepted\n");
	if (connfd < 0) {
		std::cout << "error " << errno << std::endl;
	}
	else {
		printf("Do\n");
		char buffer[BUF_SIZE];
		memset(buffer, '\0', BUF_SIZE);

		ret = recv(connfd, buffer, BUF_SIZE - 1, MSG_OOB);
		std::cout << "Get " << ret << " bytes, is " << buffer << std::endl;

		close(connfd);
	}

	printf("Ready to close");
	getchar();
	close(sock);

	return 0;
}

Client

int main() {
	const char* ip = "Your IP address";
    int port = 19980;
    sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_port = htons(port);
    inet_pton(AF_INET, ip, &address.sin_addr);

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    perror("socket");
    assert(sock >= 0);
    int ret = connect(sock, (sockaddr*)&address, sizeof(address));
    perror("connect");
    if(ret < 0){
        std::cout<<"connect fail"<<std::endl;
    }else{
        const char* data = "abc";
        send(sock, data, strlen(data), 0);
        std::cout<<"OK"<<std::endl;
    }

    close(sock);
    return 0;
}

通用数据读写函数

#include<sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);

struct msghdr{
	void* msg_name; // socket address
	socklen_t msg_namelen; // length of socket address
	struct iovec* msg_iov; // 分散的内存块
	int msg_iovlen; // 分散内存块的数量
	void* msg_control; // 辅助数据的起始位置
	socklen_t msg_controllen; // 辅助数据的大小
	int msg_flags; // 复制函数中的flags参数,并在调用过程中更新
};

在这里插入图片描述


版权声明:本文为xzzxwmsl原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。