TCP/IP协议程序步骤

TCP概述

TCP是面向连接、可靠的、基于字节流的传输层通信协议,需要通信双方首先建立一个连接。但它对系统资源要求比较高。

TCP 建立连接 (三次握手)

客户端 服务端 SYN(x) SYN(y)-ACK(x+1) ACK(y+1) 客户端 服务端

TCP 连接中止( ​四次挥手)

客户端 服务端 FIN(m) ACK(m+1) FINFIN(n) ACK(n+1) 客户端 服务端

TCP服务端程序的开发流程如下所示

// 初始化Winsock库,获得协议版本
// 创建服务Socket对象 (指定协议类型,地址族信息)
// 绑定Socket (将指定的IP,端口绑定给Socket)
// 开始监听,并且设置监听数量. (开始监听后,客户端就可以连接成功)
// 开启端口,接收连接
// 收发数据(利用建立连接的Socket对象进行通信)
// 关闭Socket连接
// 终止Winsock库的调用

开发一个TCP服务端程序,在完成初始化Winsock库和后,还要完成如下步骤。
(1)创建套接字对象指定协议类型、地址族信息
socket接口:

int socket(
	int domain,
  	int type,
	int protocol
);
  1. 参数domain:指协议族,常见的值有:
    AF_INET,指定so_pcb中的地址要采用ipv4地址类型
    AF_INET6,指定so_pcb中的地址要采用ipv6的地址类型
    AF_LOCAL/AF_UNIX,指定so_pcb中的地址要使用绝对路径名
    AF_ROUTE,
    当然也还有其他的协议族,用到再学习了
  2. 参数type:指定socket的类型,也就是上面讲到的type字段,比较常用的类型有:
    SOCK_STREAM、 (TCP)
    SOCK_DGRAM、 (UDP)
    SOCK_RAW、
    SOCK_PACKET、
    SOCK_SEQPACKET、
  3. 参数protocol:指定具体的协议,UDP也就是指定本次通信能接受的数据包的类型和发送数据包的类型,常见的值有:
    IPPROTO_TCP,(TCP协议)
    IPPROTO_UDP,(UPD协议)
    IPPROTO_SCTP,(STCP传输协议)
    IPPROTO_TIPC,(TIPC传输协议)
    0,如果指定为0,表示由内核根据type指定默认的通信协议

(2)绑定套接字到指定IP地址和端口

无论是使用哪种协议的服务端程序,都要将服务端的IP地址和端口绑定给先前创建的套接字,客户端程序将与之进行通信。绑定套接字的函数是bind,原型如下。

int bind(
 	SOCKET s,                           //套接字句柄
 	const struct sockaddr FAR *name,    //要绑定的地址
	int namelen                         //name所指定的地址长度
);
  1. 参数s: 是要绑定地址的套接字句柄,由socket函数返回。

  2. 参数name: 是指向sockaddr结构体的指针,用来指定套接字所绑定的地址。

    对于TCP、UDP协议,该地址通常是IP地址和端口号,对于TCP、UDP协议使用sockaddr_in结构体 代替sockaddr。其定义如下。
    不同的协议族创建的套接字使用的地址
    IPV4:

struct sockaddr_in {
short sin_family; //协议族,AF_INET
u_short sin_port; //端口号
struct in_addr sin_addr; //IP地址
char sin_zero[8]; //占位值,通常为0
};
struct in_addr {
uint32_t s_addr; //IP地址
};

ipv6对应的是:

struct sockaddr_in6 {
sa_family_t sin6_family; //协议族,AF_INET6
in_port_t sin6_port; //端口号
uint32_t sin6_flowinfo; IPv6流信息
struct in6_addr sin6_addr; //IP地址
uint32_t sin6_scope_id; //Scope ID (new in 2.4)
};
struct in6_addr {
unsigned char s6_addr[16]; //IP地址
};

Unix域对应的是:

#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; //协议族,AF_UNIX
char sun_path[UNIX_PATH_MAX]; //pathname
};

     a、成员sin_family同socket函数的af参数。
     b、成员sin_port指定了TCP或UDP通信服务的端口号。应用程序选择端口号时应该注意,如0~1023由
     IANA (Internet Assigned Numbers Authorith)管理,保留为公共服务使用,普通用户应用程序
     应该选择1024以上的端口号。同时需要注意,这里的值使用的是网络字节顺序(大端模式),而计算机中
     存储的数字是主机字节顺序小端模式)。 Winsock库提供了一系列转换函数用于两种顺序之间的转换,
     如下所示。
u_short htons(u_short hostshort); //将u_short类型由主机字节顺序转换为网络字节顺序
u_long htonl(u_long hostlong);    //将u_long类型由主机字节顺序转换为网络字节顺序
u_short ntohs(u_short netshort);  //将u_short类型由网络字节顺序转换为主机字节顺序
u_long ntohl(u_long netlong);     //将u_long类型由网络字节顺序转换为主机字节顺序
 例如我们要使用端口6000,那么应该是sin_port = htons(6000);

     c、成员sin_addr用来存储IP地址,它被定义为一个联合来处理整个32位的值,定义如下。
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;

我们知道IP地址是一个32位数据,即四个字节构成,每个字节对应由点分开的每个部分。所以每部分的最大值是255。如果直接使用字符串为此成员赋值,通常需要进行转换,Winsock库提供了如下转换函数。

unsigned long inet_addr(const char  FAR *cp);//将用字符串表示的IP地址准换为网络字节顺序的32位值
char FAR * inet_ntoa(struct  in_addr in);//将用网络字节顺序32位值表示的IP地址转换为用字符串表示的IP地址
   d、成员sin_zero是为了与sockaddr结构大小相同而设置的,没有其他含义。

函数执行成功则返回0,否则返回SOCKET_ERROR,使用WSAGetLastError函数获得错误代码。
(3)监听连接

当socket完成bind指定IP地址和端口号后就应该设置socket进入监听状态,socket()函数创建的socket默认是一个主动类型的,使用listen函数将socket变为被动类型的,等待客户的连接请求。使用listen函数,原型如下。

int listen(
 SOCKET s,   //套接字句柄
 int backlog  //监听队列中允许保持尚未处理的最大连接数量
);

当服务端设置套接字进入监听状态,并且排队尚未满的情况下,客户端的连接函数就可以连接成功,否则客户端的连接函数将处理阻塞状态,直到连接成功或超时。
(4)接受连接请求
当服务端套接字进入监听状态后,接着应该使用accept函数接受排队等候的连接,如果有排队等候连接则立即返回完成连接,否则accept函数将进入阻塞状态,直到有连接到来,或者socket对象被关闭。accept函数原型如下。

SOCKET accept(
SOCKET s, //套接字句柄
struct sockaddr FAR *addr, //指向sockaddr_in结构的指针,用于接收对方的地址
int FAR *addrlen     //addr指针指向内存的大小
);

注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。socket通信原理
(5)read/write

(6)关闭连接

TCP客户端程序的开发流程如下所示

// 初始化Winsock库,获得协议版本
// 创建服务Socket对象 (指定协议类型,地址族信息)
// 连接(向指定IP和端口的服务器进行连接)
// 收发数据(利用建立连接的Socket对象进行通信)
// 关闭Socket连接
// 终止对Winsock的调用

而对于TCP客户端程序的开发,在完成初始化Winsock库和创建套接字(Socket)对象两个通用步骤后,接下来就是使用connect函数发起对服务端的连接,函数原型如下。

int connect(SOCKET s,            //套接字句柄
const struct sockaddr FAR *name, //指向sockaddr_in结构的指针,指定要连接的服务器的地址 
int namelen  //addr指针指向内存的大小
);

函数执行成功返回0,之后就可以利用套接字s进行收发数据。函数执行失败返回SOCKET_ERROR,使用WSAGetLastError函数进一步获得错误代码。

无论是TCP协议的服务端程序还是客户端程序,发送数据都是使用send函数,原型如下。

int send(
 SOCKET s,               //已建立连接的套接字句柄
 const char FAR *buf,    //要发送的内容所在内存首地址
 int len,                //发送内容的长度
 int flags               //指定调用方式,通常置为0
);

函数执行成功后返回实际发送数据的字节数。

接收数据使用recv函数,原型如下。

int recv(
 SOCKET s,          //已建立连接的套接字句柄
 char FAR *buf,     //要接收的内容所在内存首地址
 int len,           //接收数据缓冲区的长度
 int flags          //指定调用方式,通常置为0
);

函数执行成功后返回实际接收数据的字节数。在阻塞模式下,recv将阻塞线程的执行,直至接收到数据。

引自:TCP程序开发步骤

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值