1 常用缩写
a -- address
f -- file eg: fputs() -- file put stream
fd -- file descriptor
h - host(主机)
in/inet -- internet eg: sockaddr_in; inet_aton
n -- network(网络字节序)/numeric(数值)
p -- protocol(协议)/presentation(表达/呈现形式)
s -- socket eg: sin -- socket internet
t -- type,用于指定某种类型,很多情况下无特殊含义
u -- unsigned(无符号) eg: uint16 -- unsigned int 16 bits
2 常见类型
SA -- struct sockaddr -- 通用套接字地址结构
socklen_t,套接字地址结构长度,一般为uint32_t
sa_family_t,在支持长度字段(sin_len)中是8位无符号整型,不支持则是16位
in_addr_t, ipv4地址,至少32位的无符号整型,一般为uint32_t
in_port_t, tcp/udp端口,至少16位的无符号整型一般为unit16_t
3 套接字与套接字函数
套接字(socket)是一种通信端点,用于在网络中进行数据传输。在网络编程中,套接字是一个抽象的概念,通常用于创建、配置和管理网络连接。套接字负责处理网络通信的细节,包括建立连接、传输数据和断开连接等
套接字函数是在网络编程中常用的一类函数,用于创建、管理和操作套接字,实现网络通信。套接字函数以引用的形式传递套接字地址结构,相应的参数是一个指向套接字地址结构的指针。
进程到内核的套接字函数:bind(), connect(), sendto()
内核到进程的套接字函数:accept(), recvfrom(), getsockname(), getpeername()
关于进程和内核在本文不做进一步探讨。
4 套接字地址结构
POSIX:可移植操作系统接口(Portable Operating System Interface of UNIX),是由IEEE定义的一系列标准
前文提到,大多数套接字函数需要一个指向套接字地址结构的指针,而这些结构的名字均以sockaddr_开头。下列套接字结构均采用POSIX定义:
4.1 ipv4套接字地址结构sockaddr_in
struct in_addr
{
in_addr_t s_addr; //uint32_t,表示ipv4地址
//t是一个无明确含义的后缀
//网络字节序
};
struct sockaddr_in //标*为POSIX规范必要字段
{
uint8_t sin_len; //长度字段,16字节
sa_family_t sin_family; //*协议族,AF_INET4
in_port_t sin_port; //*uint16_t,表示端口号
struct in_addr sin_addr; //*上面已定义
char sin_zero[8]; //未使用,零填充
};
4.2 通用套接字地址结构sockaddr/SA
struct sockaddr //对指向特定协议的sa结构进行强制类型转换
{
unit8_t sa_len;
sa_family_t sa_family;
char sa_data[14]; //与特定协议相关的地址信息
};
4.3 ipv6套接字地址结构sockaddr_in6
struct in6_addr
{
uint8_t s6_addr[16]; //128bit,表示ipv6地址
//网络字节序
};
#define SIN6_LEN
struct sockaddr_in6
{
uint8_t sin6_len; //长度字段,28字节
sa_family_t sin6_family; //协议族,AF_INET6
in_port_t sin6_port; //网络字节序,表示端口号
uint32_t sin6_flowinfo; //流信息,通常置0
struct in6_addr sin6_addr; //上面已定义
uint32_t sin6_scope_id; //标识接口的范围
};
5 字节操纵函数
在处理套接字地址结构时,对字节进行处理的函数
5.1 以b(byte)开头的
void bzero(void *dest, size_t nbytes);
void bcopy(const void *src, void *dest, sieze_t btypes);
int bcmp(const void *ptrl, const void *ptr2, size_t nbytes);
bzero:把dest字符串中nbytes个字节置0,用于初始化套接字地址结构
bcopy:将btypes个字节从src原地址移到dest目的地址
bcmp:比较两个字符串,相同则返回0,不同返回非0
5.2 以mem(memory)开头的
void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src, size_t nbytes);
void memcmp(const void *ptrl, const void *ptrl2, size_t nbytes);
memset:把dest字符串中len个字节置为c
memcpy:与bcopy类似,但先目的地址再源地址,与赋值语句的顺序一致
memcmp:比较两个字符串,相同则返回0,ptrl1比ptrl2大则返回正数(想必大家c语言都学过)
6 地址转换函数
6.1. 地址字符串与网络字节序二进制值的转换
int inet_aton(const char *strptr, struct in_addr *addrptr);
inet_aton:有效返回1,无效返回0
in_addr_t inet_addr(const char *strptr);
inet_addr:有效则返回32位二进制网络字节序的ipv4地址,否则返回INADDR_NONE
char *inet_ntoa(struct in_addr inaddr);
inet_ntoa:返回一个点分十进制的指针
6.2 表达(ASCII字符串)与数值(二进制值)的转换
int inet_pton(int family, const char *strptr, void *addrptr);
inet_pton:有效返回1,无效返回-1
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
inet_ntop:有效返回指针,否则返回NULL
7 时间获取程序
7.1 一个简单的客户端程序(ipv4/ipv6)
#include "unp.h"
int main(int argc, char **argv)
{
int sockfd;
//创建文件描述符,返回一个整型来唯一标识一个打开的文件
int n;
//是read函数的返回值,代表从套接字中读取到的字节数
char recvline[MAXLINE + 1];
//确保数组可以容纳最大长度为MAXLINE的字符串,并在末尾存储终止符\0
struct sockaddr_in servaddr; //ipv4
//struct sockaddr_in6 servaddr; //ipv6
//创建套接字表示服务器IPv4/ipv6端口号和地址
if (argc != 2) //如果命令行输入的参数数量不是2,则出错
err_quit("usage: a.out <IPaddress>");
if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
//if ( (sockfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0)【ipv6】
err_sys("socket error");
//socket函数三个参数代表ipv4,面向连接的tcp套接字
//0表示使用默认的协议,对tcp来说通常是0
//返回值sockfd若为-1则创建套接字失败
//这两行代码可以用包裹函数Socket()等效代替,如后文服务器代码中所示
bzero(&servaddr, sizeof(servaddr));
//将从起始位置到sizeof()大小的内存区域置0,可用下面的代码代替
// memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET; //地址族为ipv4
//servaddr.sin6_family = AF_INET6; //地址族为ipv6
servaddr.sin_port = htons(13);
//servaddr.sin6_port = htons(13);【ipv6】
//用sin_port来存储端口号,这里为网络字节序中的13
//用htons()将主机字节序(可能是大端或者小端)转换为网络字节序(大端)
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
//if (inet_pton(AF_INET6, argv[1], &servaddr.sin6_addr) <= 0)【ipv6】
err_quit("inet_pton error for %s", argv[1]);
//用inet_pton()将第二个命令行参数(ip地址)转换为二进制
//并存储在seraddr.sin_addr中
if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
err_sys("connect error");
//建立一个连接到远程服务器的套接字连接
//SA即为struct sockaddr通用套接字
while ( (n = read(sockfd, recvline, MAXLINE)) > 0)
{
recvline[n] = 0; //把末尾数据清零
if (fputs(recvline, stdout) == EOF)
err_sys("fputs error");
//将从服务器读取的数据写入标准输出流(stdout)
//fputs返回非负数则成功,返回负数代表错误(End Of File)
}
//从套接字中读取至多MAXLINE个字,
//若n等于0则读到了文件末尾
if(n<0)
err_sys("read error");
exit(0);
}
7.2 一个简单的服务器程序(ipv4)
看完客户端代码,服务器代码也就大同小异了文章来源:https://www.toymoban.com/news/detail-794571.html
#include "unp.h"
#include <time.h>
int
main(int argc, char **argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
time_t ticks;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
//用listen()转化为监听套接字,ipv4,tcp
//将套接字函数的首字母大写,则变成了对应的具有错误检测功能的包裹函数
bzero(&servaddr, sizeof(servaddr));
//将从起始位置到sizeof()大小的内存区域置0
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//指定ip地址为INADDR_ANY,即能在任意网络接口上监听客户连接
servaddr.sin_port = htons(13); /* daytime server */
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
//将监听套接字绑定到服务器地址上
//izeof(servaddr)表示要绑定的地址信息的长度
Listen(listenfd, LISTENQ);
//LISTENQ:常数,表示系统内核允许在这排队的最大客户连接数
for ( ; ; ) //无限循环
{
connfd = Accept(listenfd, (SA *) NULL, NULL);
//Accept:阻塞函数,当没有连接请求的时候会一直等待
//(SA*) NULL:表示不获取客户端的地址信息
//NULL:表示不获取客户端地址的地址长度参数
//confid:接受Accept返回的套接字文件描述符
ticks = time(NULL);
//获取当前时间,并将当前秒数返回给ticks
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
//格式化输出,写入buff这个指定的缓冲区
Write(connfd, buff, strlen(buff));
//向已建立连接的文件描述符connfd写入数据
Close(connfd);
}
}
至此,对这个简单的客户端和服务器程序应该有了较为全面的理解。文章来源地址https://www.toymoban.com/news/detail-794571.html
到了这里,关于unix网络编程-简易服务器与客户端程序解析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!