Socket 是一种通信机制,通过它可以在不同主机之间进行数据交换。
在Socket 编程中,有两种常见的通信模式:客户端-服务器模式 和 点对点模式。
它基于 TCP/IP 协议栈,并使用 IP地址 和 端口号 来标识通信的目标。
Socket 编程可以通过 TCP(传输控制协议) 和 UDP(用户数据报协议) 实现不同类型的连接。
socket 接口 创建的是文件,不同主机通过这个接口进行通信,即 网络通信依附的也是文件系统
netstat -naup
:
-n:把相应信息显示成数字
-a:显示所有进程
-u:显示 upd
-p:显示 进程
netstat -nltp
:
-n:把相应信息显示成数字
-l:显示状态为监听 listen 的进程
-t:显示 tcp
-p:显示 进程
端口号是传输层协议的内容
数据类型为 uint16_t
,即 16 位的 2 字节整数
一个端口号只能标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理
IP地址 + 端口号,就能够标识网络上的某一台主机的某一个进程
注意:进程 pid 是唯一表示的,端口号也是唯一表示的,没有必然关系,就好比身份证和工号自己标记自己的
讨论网络字节序还是主机字节序的意义,保证了主机能正确收发数据信息
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,这需要接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存。
网络数据流的地址这样规定:先发出的数据是低地址,后发出的数据是高地址。
网络字节序和主机字节序的转换:
#include
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohl(uint16_t hostshort);
h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数。
接收和发送端口号,用这些接口转化
例如:htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
是用于储存网络地址信息的结构体,实现了多态。
IPv4 和 IPv6 的地址格式定义在 头文件 netinet/in.h
中
IPv4 地址用 sockaddr_in
结构体表示,包括:
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_); /*16位地址类型*/
in_port_t sin_port; /* Port number.端口号,无符号16位整数*/
struct in_addr sin_addr; /* Internet address.IP 地址,无符号32位整数*/
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
/* This macro is used to declare the initial common members
of the data types used for socket addresses, `struct sockaddr',
`struct sockaddr_in', `struct sockaddr_un', etc. */
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
IPv4 地址类型 定义为常数 AF_INET
IPv6 地址类型 定义为常数 AF_INET6
这样,只要取得某种 sockaddr 结构体的首地址不需要知道具体是哪种类型的 sockaddr 结构体,就可以根据地址类型字段确定结构体中的内容。
这样的好处是程序的通用性,可以接收 IPv4、IPv6、以及 UNIX Domain Socket 各种类型的 sockaddr 结构体指针做为参数
// 字符串风格的 IP 地址,转换成为 4 字节 int,同时将主机序列转化成为网络序列
in_addr_t inet_addr(const char *cp);
// 4 字节 int,转化为字符串风格 IP 地址
char *inet_ntoa(struct in_addr in);
// string 类型 IP 地址,转换成网络序列的 4 字节 IP 地址
// &struct sockaddr.sin_addr(点的优先级高)
int inet_aton(const char *cp, struct in_addr *inp);
#include
#include
网络通信依附的也是文件系统!创建的是文件描述符。
int socket(int domain, int type, int protocol);
参数 domian(地址类型,即选择通信方式):
- AF_INET,IPv4 网络通信
- AF_INET6,IPv6 网络通信
- AF_UNIX,本地通信
参数 type:
- SOCK_STREAM,流式套接(TCP)
- SOCK_DGRAM,用户数据报(支持无连接、不可靠的数据通信,UDP)
参数 protocol:
- 默认设为 0,就可以自动识别是 TCP 还是 UDP
返回值:
- 创建成功返回文件描述符,创建失败返回 -1,并设置错误码
比如我们在实现某个函数时对 socket 进行了填充,可是创建的都是临时变量,于是就需要使用 bind 对填充数据进行于 socket 的绑定
#include
#include
#include
#include
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
参数 socket:
- 套接字的文件描述符
参数 address:
- 用户自定义的用于填充数据的结构体,需要强转成 (struct sockaddr*) 使用
参数 address_len:
- 实际传入 address 的大小,即 sizeof(address)
返回值:
- 成功返回 0 ,失败返回 -1
#include
#include
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
参数 sockfd:
- 服务端所绑定的套接字,后续接收和访问都从这里来
参数 buf:
- 未来读到的数据要放在哪一个用户或者缓冲区里
参数 len:
- 缓冲区的长度
参数 flags:
- 0,默认以阻塞方式读取
参数 src_addr:
- 接收缓冲区,获取到客户端的 IP 和 端口号,需要强转成 (struct sockaddr*) 使用
参数 addrlen:
- 接收缓冲区的大小的地址
返回值:
- 成功则返回接收的字节数,失败返回 -1 并设置错误码
#include
#include
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
参数 sockfd:
- 服务端所绑定的套接字,后续接收和访问都从这里来
参数 buf:
- 未来发送的数据是哪一个用户或者缓冲区里的
参数 len:
- 缓冲区的长度
参数 flags:
- 0,默认以阻塞方式调用
参数 dest_addr:
- 发送缓冲区,需要发送到哪一个客户端,需要强转成 (struct sockaddr*) 使用
参数 addrlen:
- 接收缓冲区的大小
返回值:
- 成功则返回发送的字节数,失败返回 -1 并设置错误码
#include /* See NOTES */
#include
int listen(int sockfd, int backlog);
参数 sockfd:
- 监听的套接字的文件描述符
参数 backlog:
- 一个整数
返回值:
- 成功返回 0,失败返回 -1,并设置错误码
#include
#include
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
参数 socket:
- 监听套接字的文件描述符
参数 address:
- 接收缓冲区,获取到客户端的 IP 和 端口号,需要强转成 (struct sockaddr*) 使用
参数 address_len:
- 接收缓冲区的大小的地址
返回值:
- 如果接收成功,返回一个文件描述符(这一个套接字是完成具体业务的),接收失败则返回 -1,并设置错误码
ps:tcp 是面向流的,可以用;udp 是面向数据报的,不可以用
#include
ssize_t read(int fd, void *buf, size_t count);
返回值:
- 成功返回读取到的字符数,对方关闭连接返回 0,读取失败返回 -1,并设置错误码
ps:tcp 是面向流的,可以用;udp 是面向数据报的,不可以用
#include
ssize_t write(int fd, const void *buf, size_t count);
返回值:
- 成功返回写入的字符数,写入失败返回 -1,并设置错误码
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数 sockfd:
- 发起连接的套接字的文件描述符
参数 addr:
- 记录要发送对象的信息缓冲区,转成 (struct sockaddr *) 可以使用
参数 addrlen:
- 缓冲区的大小
返回值:
- 成功返回 0,失败返回 -1,并设置错误码
使用和 read 几乎一样。
#include
#include
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags);
参数 sockfd:
- 服务端所绑定的套接字,后续接收和访问都从这里来
参数 buf:
- 未来读到的数据要放在哪一个用户或者缓冲区里
参数 len:
- 缓冲区的长度
参数 flags:
- 0,默认以阻塞方式读取
返回值:
- 成功则返回接收的字节数,失败返回 -1 并设置错误码