TCP / IP 网络编程

本文介绍了TCP/IP网络编程,包括Windows和Linux环境下的服务器端和客户端套接字创建过程,详细解析了bind函数、字节序转换以及TCP/IP工作原理,如链路层、IP层和TCP层的功能。

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

本文大部分内容是基于 window 来写的,会介绍一小部分 Linux 内容。两者相差不多,既是您是做 Linux 编程,相信看完这篇文章也会对您有很大帮助的

网络编程中接受连接请求的服务器端套接字创建过程可整理如下。

  • 第一步:调用 socket 函数创建套接字。
  • 第二步:调用 bind 函数分配 IP 地址和端口号。
  • 第三步:调用 listen 函数转为可接收请求状态。
  • 第四步:调用 accept 函数受理连接请求。
  • 第五步:调用 recv/send 交换数据
  • 第六步:调用 closesocket 断开连接

网络编程中请求连接的客户端端套接字创建过程可整理如下。

  • 第一步:调用 socket 函数创建套接字。
  • 第二步:调用 connect 函数请求连接。
  • 第三步:调用 recv/send 交换数据
  • 第四步:调用 close 断开连接

一、 基于Linux网络编程中I/O数据函数

#include <unistd.h>

ssize_t write(int fd, const void * buf, size_t nbytes);

→成功时返回写入的字节数,失败时返回-1.
→fd      显示数据传输对象的文件描述符。
→buf     保存要传输数据的缓冲地址值
→nbytes  要传输数据的字节数
---------------------------------------------------------------------------------

ssize_t read(int fd, void * buf, size_t nbytes);

→成功时返回接收的字节数(但遇到文件结尾则返回 0),失败时返回-1.
→fd      显示数据接收对象的文件描述符。
→buf     保存要接收数据的缓冲地址值
→nbytes  要接收数据的字节数
→此函数定义中,size_t 是通过 typedef 声明的 unsigned int 类型。对 ssize_t 来说,
 size_t 前面多加的 s 代表 signed,即 ssize_t 是通过 typedef 声明的 signed int类型。

基于 Windows 的 I/O 函数

#include <winsock2.h>

int send(SOCKET s, const char * buf, int len, int flags);

→成功时返回传输字节数,失败时返回 SOCKET_ERROR 
→s     表示数据传输对象连接的套接字句柄值
→buf   保存待传输数据的缓冲地址值
→Len   要传输的字节数
→flags 传输数据时用到的多种选项信息

----------------------------------------------------------------
int recv(SOCKET s, const char * buf, int len, int flags);

→成功时返回接收的字节数(收到 EOF 时为 0),失败时返回 SOCKET_ERROR 
→s     表示数据接收对象连接的套接字句柄值
→buf   保存接收数据的缓冲地址值
→Len   能够接收的最大字节数
→flags 接收数据时用到的多种选项信息

二、创建套接字函数

#include <winsock2.h>

SOCKET socket(int af, int type, int protocol);

→成功时返回 socket 句柄,失败时返回 INVALID_SOCKET   //invalid[ˈɪnvəlɪd;ɪnˈvælɪd] 无效的
→af        套接字中使用的协议族(protocol family)信息。
→type      套接字数据传输类型信息
→protocol  计算机通信中使用的协议信息

--------------------------------------------------------------------------------------
面向连接的套接字
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
面向消息的套接字
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
声明的协议族
名称

协议族

PF_INETIPv4 互联网协议族
PF_INET6IPv6 互联网协议族
PF_LOCAL

本地通信的 UNIX 协议族

PF_PACKET底层套接字的协议族
PF_IPXIPX Novell 协议族
套接字类型 type
套接字类型性质

面向链接的套接字 SOCK_STREAM

又称为 TCP 编程

传输过程中数据不会消失

按序传输数据

传输的数据不存在数据边界(传输次数和接收次数可以不相等)

面向消息的套接字SOCK_DGRAM(datagrams)

又称为 UDP 编程

强调快速传输而非顺序传输

传输的数据可能丢失也可能损毁

传输的数据与数据边界(传输几次就要接收几次)

限制每次传输的数据大小

三、网络地址初始化、分配地址信息和端口

SOCKET servsock;
struct sockaddr_in servAddr;
char * serv_port = "9190";

/*创建服务器端套接字*/
servSock = socket(PF_INIT, SOCK_STREAM, 0);

/*地址信息初始化*/
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INIT;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(serv_port));

/*分配地址信息*/
bind(servSock, (struct sockaddr * )&servAddr, sizeof(servAddr));

下面一次对上面语句进行讲解,首先对bind函数进行详解

#include <winsock2.h>

int bind(SOCKET s, const struct sockaddr FAR * name, int namelen);

→成功时返回 0,失败时返回-1.
→s        套接字标识
→name     是一个 sockaddr 结构指针,该结构中包含了要结合的地址和端口号
→namelen  确定 name 缓冲区的长度


bind 函数中结构体详解

#include <winsock2.h>

struct sockaddr_in
{
    sa_family_t       sin_family;    //地址族(address family)
    uint16_t          sin_port;      //16 位 TCP/UDP 端口号
    struct in_addr    sin_addr;      //32 位IP 地址
    char              sin_zero[8];   //不使用
};

struct in_addr 
{
        union 
        {
            struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
            struct { u_short s_w1,s_w2; } S_un_w;
            u_long S_addr;
        } S_un;
#define s_addr  S_un.S_addr
                                /* can be used for most tcp & ip code */
#define s_host  S_un.S_un_b.s_b2
                                /* host on imp */
#define s_net   S_un.S_un_b.s_b1
                                /* network */
#define s_imp   S_un.S_un_w.s_w2
                                /* imp */
#define s_impno S_un.S_un_b.s_b4
                                /* imp # */
#define s_lh    S_un.S_un_b.s_b3
                                /* logical host */
};

struct sockaddr
{
    unsigned short sa_family;     //address family, AF_xxx
    char sa_data[14];             //14 bytes of protocol address
};


/*
sockaddr_in 是保存 IPv4 地址信息的结构体。而结构体 sockaddr 并非只为 IPv4 设计,
这从保存地址信息的数组 sa_data 长度为 14 字节也可以看出来。因此,结构体 sockaddr
要求在sin_family 中指定地址族信息。为了与 sockaddr 保持一致,sockaddr_in 结构体
中也有地址族信息。
*/

字节序转换(为什么转换请读者自行百度,这里不做介绍)

#include <winsock2.h>

u_short htons(u_short hostshort);
→功  能:把 short型数据从主机字节序转化为网络字节序。
→返回值:16 位的网络排列方式数据

u_long htonl(u_long hostlong);
→功  能:把 无符号长整型型数据从主机字节序转化为网络字节序。
→返回值:32 位的网络排列方式数据

int atoi (const char * str);
→功  能:把 ascll 码转换为网络字节序
→返回值:成功时,函数将转换后的整数作为int值返回。

--------------------------------------------------------
注1:s 指的是 short,l 指的是long。因此 htons 是 h、to、n、s
     的组合,即 host to network short
注2: 利用常数 INADDR_ANY 分配服务器地址,可以自动获取运行服务器端的计算机 IP 地址,不必亲自输入。
     而且,若统一计算机中已分配多个 IP 地址,则只要端口号一致,就可以从不同 IP 地址接收数据。

四、server/client 建立连接

在详解 TCP/IP 工作方式之前,我认为有必要介绍一下网络传输中我们编程所涉及到的层,这样有助于我们更好的理解。

  • 链路层

链路层是物理链接领域标准化的结果,也是最基本的领域,专门定义 LAN、WAN、MAN 等网络标准。若两台主机通过网络进行数据交换,则需要下图所示的物理链接,链路层就负责这些标准。

网络结构连接图
  • IP 层

为了在复杂的网络中传输数据,首先要考虑路径的选择。向目标传输数据需要经过那条路径是 IP 层解决的问题。IP 层本身是面向消息的、不可靠的协议。每次传输数据时会帮我们选择路径,但并不一致。如果传输中发生路径错误,则选择其他路径;如果发生数据丢失或错误,则无法解决。IP 层协议无法应对数据错误。

  • TCP 层

 TCP 层以 IP 层提供的路径信息为基础完成实际的数据传输,故该层又称为数据传输层(transport)。IP 层只关注一个数据包的传输过程。因此,机试传输多个数据包,每个数据包也是由 IP 层实际传输的,也就是说传输顺序及传输本身并不可靠。若只利用 IP 层传输数据,则有可能导致后传输的数据包 B 比先传输的数据包 A 提早到达。另外,传输的数据包 A、B、C 中有可能只收到 A 和 C,甚至收到的 C 可能已损毁。而 TCP 正是起到了保证不发生如上问题的作用。

传输控制协议

总之,TCP 存在于 IP 层之上,决定主机之间的数据传输方式,TCP 协议确认后向不可靠的 IP 协议赋予可靠性。

#include <winsock2.h>

int listen(SOCKET s, int backlog);
→成功时返回 0,失败时返回 -1
→s        希望进入等待连接请求状态的套接字标识符,传递的标识符套接字参数成为
          服务器端套接字/监听套接字。
→backlog  连接请求等待队列的长度,若为 5,则队列长度为 5,表示最多使用 5 个链接请求进入队列。

SOCKET accept(SOCKET s, struct sockaddr FAR * addr, int FAR * addrlen);
→成功时返回创建的套接字标识符,失败时返回-1.
→s        服务器套接字标识符,它应处于监听状态。
→addr     保存发起链接请求的客户端地址信息的变量地址值,调用函数后向传递来的地址变量参数填充
          客户端地址信息。
→addrlen  第二个参数 addr结构体的长度,但是存有长度的变量地址。函数调用完成后,该变量即被填充
          入客户端地址长度。

int connect(SOCKET s, struct sockaddr FAR * name, int FAR namelen);
→成功时返回0,失败时返回-1.
→sock     服务器套接字标识符
→addr     保存 目标服务器端地址信息的变量地址值。
→addrlen  第二个参数 addr 结构体变量的长度。

int closesocket(SOCKET s);
→成功时返回创建的套接字标识符,失败时返回-1.
→s        服务器套接字标识符

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值