网络基础 网络编程套接字

认识IP地址

  • IP地址有两个版本IPv4(32位)和IPv6(128位);
  • IP地址是在IP协议中, 用来标识网络中不同主机的地址;
  • 对于IPv4来说, IP地址是⼀个4字节, 32位的整数;
  • 我们通常也使⽤ "点分十进制" 的字符串表⽰IP地址, 例如 192.168.0.1 ; 用点分割的每⼀个数字表示⼀个字节, 范围是 0 - 255;

认识端口号

端口号(port)是传输层协议的内容,标示了这台机器上唯一的进程。

  • 端口号是⼀个2字节16位的整数;
  • 端口号⽤来标识⼀个进程, 告诉操作系统, 当前的这个数据要交给哪⼀个进程来处理;
  • IP地址 + 端口号能够标识网络上的某⼀台主机的某⼀个进程;
  • ⼀个端口号只能被⼀个进程占用

套接字(socket)

套接字用(IP地址:端口号)表示。

为什么要有套接字,举个例子:

邮寄一封信,我们不仅需要知道邮编号码,还需要知道收件人的门牌号,而此处的邮编号相当于IP地址,而端口号就相当于门牌号。

网络字节序

    我们已经知道,内存中的多字节数据相对于内存地址有⼤端和小端之分, 磁盘⽂件中的多字节数据相对于⽂件中的偏移地址也有⼤端小端之分, 网络数据流同样有⼤端小端之分. 那么如何定义网络数据流的地址呢?
  • 发送主机通常将发送缓冲区中的数据按内存地址从低到⾼的顺序发出;
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到⾼的顺序保存;
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是⾼地址.
  • TCP/IP协议规定,网络数据流应采⽤⼤端字节序,即低地址⾼字节.
  • 不管这台主机是⼤端机还是⼩端机, 都会按照这个TCP/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地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的⼤小端转换然后返回。
  • 如果主机是⼤端字节序,这些函数不做转换,将参数原封不动地返回。

socket编程接口

socket常见API

// 创建 socket ⽂件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

type:  TCP:SOCK_STREAM   UDP:SOCK_DGRAM

protocol:指定协议,若等于0,则用缺省的方式连接

// 绑定端⼝号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);

address:指向sockaddr结构体类型的指针     address_len:结构体长度,用sizeof获得

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);

backlog:等待连接队列的最大长度,一般设置为5-10,不可设置太长

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建⽴连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

sockaddr_in 结构体


sockaddr_in结构体内主要有三部分信息:地址类型、端口号、IP地址。 

地址转换函数

字符串转in_addr函数

#include <arpa/inet.h>
int inet_aton(const char* strptr, struct in_addr* addrptr);
in_addr_t inet_addr(const char* strptr);
int inet_pton(int family, const char* strptr, void* addrptr);

in_addr转字符串函数

char* inet_ntoa(struct in_addr inaddr);
const char* inet_ntop(int family, const void* addrptr, char* strptr, size_t len);

其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void*addrptr。

关于inet_ntoa

man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放.

那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码

#include<stdio.h>
#include<netinet/in.h>
#include<arpa/inet.h>


int main()
{
	struct sockaddr_in addr1;
	struct sockaddr_in addr2;
	addr1.sin_addr.s_addr = 0;
	addr2.sin_addr.s_addr = 0xffffffff;
	char* ptr1 = inet_ntoa(addr1.sin_addr);
	char* ptr2 = inet_ntoa(addr2.sin_addr);
	printf("prt1 = %s,ptr2 = %s\n",ptr1,ptr2);
	return 0;
	
}

因为inet_ntoa把结果放在自己内部的一个静态存储区,这样第二次调用的时候就会覆盖上一次的结果。所以如果有多个线程调用inet_ntoa时,就会出现异常,为此应调用inet_ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题。


猜你喜欢

转载自blog.csdn.net/ihaha233/article/details/80318537
今日推荐