unix网络编程-简易服务器与客户端程序解析

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)

看完客户端代码,服务器代码也就大同小异了

#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);
	}
}

 至此,对这个简单的客户端和服务器程序应该有了较为全面的理解。

  • 25
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值