UNIX网络编程(一)一个简易的TCP C/S模型(echo sever)

本文介绍了如何在UNIX系统中实现一个简单的TCP客户端和服务器模型,涉及socket接口、TCP连接的三次握手和四次挥手过程。通过socket()、bind()、listen()、accept()和connect()等函数,详细讲解了服务器端和客户端的创建、连接和通信。此外,还讨论了网络字节序与主机字节序的转换问题以及网络状态的查询方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下内容主要参考书籍《Linux C编程一站式学习》、《Unix网络编程》、《Unix高级环境编程》

首先要明确客户端与服务器要怎么去实现通信
下图便是一个简易的TCP C/S模型实现
这里写图片描述

知道模型之后,接下来只是一些与网络接口相关的API调用。

预备知识:
socket是什么?

1.在TCP 在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP地址+端口号”就称为socket。
2.在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。socket本身有“插座”的意思,因此用来描述网络连接的一对一关系。
3.TCP/IP协议最早在BSD UNIX上实现,为TCP/IP协议设计的应用层编程接口称为socket接口
TCP4层模型与socket接口之间关系如下图
这里写图片描述

具体相关的函数接口
1.socket()

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
各参数解释如下

domain:
    AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
    AF_INET6 与上面类似,不过是来用IPv6的地址
    AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用

type:
    SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。当将protocol设为0时,则默认使用TCP来进行传输。
    SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,当将protocol设为0时,则默认使用UDP来进行传输。
    SOCK_SEQPACKET 这个协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
    SOCK_RAW 这个socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
    SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序

protocol: 
    0 默认协议

返回值:
    成功返回一个新的文件描述符
    失败返回-1,并设置errno

当进程调用socket函数后,系统会为其分配一个文件描述符,供进程去在网络上进行通信,对于文件描述符,我们就有许多对应的系统调用可以使用了,比如read,write等等。
但对于服务器来说,需要将本地地址绑定一个固定端口及相关的接口,来让客户端能够方便的访问到服务器,这时就需要一个函数接口bind()。

2.bind()

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:
    socket文件描述符
addr:
    构造出IP地址加端口号
addrlen:
    sizeof(addr)长度
这个函数重点在于struct sockaddr这个结构体,由于历史原因,当前并没有void *这个向上兼容的泛型指针,而所有的这些函数参数都使用struct sockaddr *类型表示,在使用的时候需要我们强转一下。
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};

IPV4地址使用struct sockaddr_in
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */ IPV4此值取值为AF_INET
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
/* Internet address. */
struct in_addr {
__be32 s_addr;
};

IPV6使用struct sockaddr_in6
struct sockaddr_in6 {
unsigned short int sin6_family; /* AF_INET6 */  
__be16 sin6_port; /* Transport layer port # */
__be32 sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
__u32 sin6_scope_id; /* scope id (new in RFC2553) */
};
struct in6_addr {
union {
__u8 u6_addr8[16];
__be16 u6_addr16[8];
__be32 u6_addr32[4];
} in6_u;
#define s6_addr in6_u.u6_addr8
#define s6_addr16 in6_u.u6_addr16
#define s6_addr32 in6_u.u6_addr32
};

本地套接字使用struct sockaddr_un
#define UNIX_PATH_MAX 108
struct sockaddr_un {
__kernel_sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
返回值:
    成功返回0,失败返回-1, 并设置errno

当服务器将本地地址和一套接口绑定完之后,则需要给服务器设置为监听状态,使其有接受连接请求的能力,此时就需要函数接口listen()。
3.listen()

#include <sys/types.h> /* See NOTES */
#include <sys/s
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值