windows - TCP通讯流程

TCP通讯流程

在这里插入图片描述

三次握手

在这里插入图片描述

TCP建立连接的过程叫做握手
握手需要在客户和服务器之间交换三个TCP报文段,称为三次握手
采用三次握手主要是为了防止已失效的连接请求报文段 突然又传送到了,因而产生错误。

第一次握手:
客户端将TCP报文标志位SYN置为1,随机产生一个序号值seq=x,保存在TCP首部的序列号(Sequence Number)字段里,指明客户端打算连接的服务器的端口,并将该数据包发送给服务器端,发送完毕后,客户端进入SYN_SENT状态,等待服务器端确认。

第二次握手:
服务器端收到数据包后由标志位SYN=1表示知道客户端请求建立连接,同时将标志位SYN和ACK都置为1,确认号ack=x+1,随机产生一个序号值seq=y,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。

第三次握手:
客户端收到确认后,检查确认号ack是否为x+1,标志位ACK是否为1,如果正确则将标志位ACK置为1,确认号ack=y+1,并将该数据包发送给服务器端,服务器端检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了

注意:我们上面写的ack和ACK,不是同一个概念:

  • 小写的ack代表的是头部的确认号Acknowledge number, 缩写ack,是对上一个包的序号进行确认的号,ack=seq+1。
  • 大写的ACK,则是我们上面说的TCP首部的标志位,用于标志的TCP包是否对上一个包进行了确认操作,如果确认了,则把ACK标志位设置成1。

四次挥手

在这里插入图片描述

第一次挥手:
Client端发起挥手请求,并且停止发送数据,向Server端发送标志位是FIN报文段,FIN=1,设置序列号seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,Client端进入FIN_WAIT_1(终止等待1)状态,这表示Client端没有数据要发送给Server端了(TCP规定,FIN报文段即使不携带数据,也要消耗一个序号)

第二次挥手:
Server端收到了Client端发送的FIN报文段,向Client端返回一个标志位是ACK=1的报文段,ack设为seq加1(u+1),并且带上自己的序列号seq=v,服务端就进入了CLOSE-WAIT(关闭等待)状态,TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

第三次挥手:
服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

第四次挥手:
客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端依然没有收到回复,才进入CLOSED状态。

服务器只要收到了客户端发出的确认,立即进入CLOSED状态。可以看到,服务器结束TCP连接的时间要比客户端早一些。

服务器端

//绑定0端口: 分配本机空闲端口
//绑定0IP: :代表本机所有IP

WSAstartup()

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

参数wVersionRequested是Windows Sockets API提供的调用方可使用的最高版本号。高位字节指出副版本(修正)号,低位字节指明主版本号。
lpWSAData 是指向WSADATA数据结构的指针,用来接收Windows Sockets实现的细节。
功能:使用Socket的程序在使用Socket之前必须调用WSAStartup函数。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。
返回值:int类型 成功返回0

wVersionRequested 参数用来指明我们希望使用的版本号,它的类型为 WORD,等价于 unsigned short,是一个整数,所以需要用 MAKEWORD() 宏函数对版本号进行转换。例如:

MAKEWORD(1, 2);  //主版本号为1,副版本号为2,返回 0x0201
MAKEWORD(2, 2);  //主版本号为2,副版本号为2,返回 0x0202
#define MAKEWORD(a, b)      ( a | b << 8)
#define MAKELONG(a, b)      (a | b << 16)
	a=0x12
	b=0x34
	MAKEWORD(a,b)=0x3412
	a = 0x1234
	b = 0x5678
	MAKELONG(a,b)=0x56781234
    
#define LOBYTE(w)           ((BYTE)(w& 0xff))
#define HIBYTE(w)           ((BYTE)(w >> 8) & 0xff)
#define LOWORD(l)           ((WORD)(((DWORD_PTR)(l)) & 0xffff))
#define HIWORD(l)           ((WORD)((((DWORD_PTR)(l)) >> 16) & 0xffff))
	x = 0x1234
	LOBYTE(x)=0x34
	HIBYTE(x)=0x12
	y = 0x12345678
	LOWORD(y)=0x5678
	HIWORD(y)=0x1234

socket

SOCKET WSAAPI socket(
  int af, //地址族规范。 地址族的可能值在Winsock2.h头文件中定义。
  int type,//新套接字的类型规范。
  int protocol//要使用的协议。
);

返回值: returns file (socket) descriptor if OK, -1 on error.(成功返回文件描述符,失败返回-1)
#define INVALID_SOCKET (SOCKET) (~0)
#define SOCKET_ERROR (~1)
参数int af
当前支持的值为AF_INET或AF_INET6,这是IPv4和IPv6的Internet地址族格式。 如果安装了地址族的Windows套接字服务提供程序,则支持地址族的其他选项(例如,与NetBIOS一起使用的AF_NETBIOS)。 请注意,(AF_地址族和PF__协议族)常量的值是相同的(例如,AF_INET和PF_INET),因此可以使用任何一个常量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j0OqFENt-1686921166997)(D:\HomeWork\笔记\网络与数据库\存储图片\int af.png)]

int type
新套接字的类型规范。 AF_INET族的子协议:
套接字类型的可能值在Winsock2.h头文件中定义。
应用程序可以通过WSAEnumProtocols函数动态发现每个可用传输协议的属性。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5I4Z6ro8-1686921166998)(D:\HomeWork\笔记\网络与数据库\存储图片\int type.png)]

int protocol IPPROTO_IP
要使用的协议。 protocol参数的可能选项特定于指定的地址族和套接字类型。 在Winsock2.h和Wsrm.h头文件中定义了协议的可能值。
在为Windows Vista和更高版本发布的Windows SDK上,头文件的组织已更改,并且此参数可以是Ws2def.h头文件中定义的IPPROTO枚举类型的值之一。 请注意,Ws2def.h头文件自动包含在Winsock2.h中,并且绝对不能直接使用。
如果指定的值为0,则调用者不希望指定协议,服务提供商将选择要使用的协议。
当af参数为AF_INET或AF_INET6且类型为SOCK_RAW时,在IPv6或IPv4数据包头的协议字段中设置为协议指定的值。

bind()

bind() 函数的原型为:

int bind(int sock, struct sockaddr *addr, socklen_t addrlen);  //Linux
int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);  //Windows

返回值:returns 0 on success, or -1 on error.(成功返回0,失败返回-1)

sockaddr 结构体

struct sockaddr{
    u_short  sin_family;   //地址族(Address Family),也就是地址类型
    char         sa_data[14];  //IP地址和端口号
};

sockaddr 是一种通用的结构体,可以用来保存多种类型的IP地址和端口号,而 sockaddr_in 是专门用来保存 IPv4 地址的结构体。另外还有 sockaddr_in6,用来保存 IPv6 地址,它的定义如下:

struct sockaddr_in6 { 
    sa_family_t sin6_family;  //(2)地址类型,取值为AF_INET6
    in_port_t sin6_port;  //(2)16位端口号
    uint32_t sin6_flowinfo;  //(4)IPv6流信息
    struct in6_addr sin6_addr;  //(4)具体的IPv6地址
    uint32_t sin6_scope_id;  //(4)接口范围ID
};

正是由于通用结构体 sockaddr 使用不便,才针对不同的地址类型定义了不同的结构体。

sockaddr_in 结构体

struct sockaddr_in{
    short     sin_family;   //地址族(Address Family),也就是地址类型
    uint16_t        sin_port;     //16位的端口号   1-65535
    struct in_addr  sin_addr;     //32位IP地址
    char            sin_zero[8];  //不使用,一般用0填充
}SOCKADDR_IN, *PSOCKADDR_IN;
  1. sin_family 和 socket() 的第一个参数的含义相同,取值也要保持一致。
  2. sin_prot 为端口号。uint16_t 的长度为两个字节,理论上端口号的取值范围为 0~65536,但 0~1023 的端口一般由系统分配给特定的服务程序,例如 Web 服务的端口号为 80,FTP 服务的端口号为 21,所以我们的程序要尽量在 1024~65536 之间分配端口号。端口号需要用 htons() 函数转换。
  3. sin_addr 是 struct in_addr 结构体类型的变量。
  4. sin_zero[8] 是多余的8个字节,没有用,一般使用 memset() 函数填充为 0。上面的代码中,先用 memset() 将结构体的全部字节填充为 0,再给前3个成员赋值,剩下的 sin_zero 自然就是 0 了。

in_addr 结构体

sockaddr_in 的第3个成员是 in_addr 类型的结构体,该结构体只包含一个成员,如下所示:

typedef struct in_addr {
        union {
                struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b;
                struct { USHORT s_w1,s_w2; } S_un_w;
                ULONG S_addr;
        } S_un;
} IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;

in_addr_t 在头文件 <netinet/in.h> 中定义,等价于 unsigned long,长度为4个字节。s_addr 是一个整数,而IP地址是一个字符串,所以需要 inet_addr() 函数进行转换。

unsigned long ip = inet_addr("127.0.0.1");
printf("%ld\n", ip);
sockaddr_in addr_in = { AF_INET,htons(0x1234) };
addr_in.sin_addr;

listen()

int WSAAPI listen(
    SOCKET s,
    int backlog
);

参数:s:指定接收端套接字描述符,客户端的socket,每个客户对应唯一的socket;
backlog:规定了内核应该为相应套接字排队的最大连接个数。用SOMAXCONN则为系统给出的最大值
挂起连接队列的最大长度。 如果设置为 SOMAXCONN,则 负责套接字的基础 服务提供商会将积压工作设置为最大合理值。 如果设置为 SOMAXCONN_HINT (N) ((其中 N 为数字) ),积压工作值将为 N,调整为 (介于 200、65535) 范围内。 请注意, SOMAXCONN_HINT 可用于将积压工作设置为大于 SOMAXCONN 的值。
SOMAXCONN_HINT 仅受 Microsoft TCP/IP 服务提供商支持。 没有用于获取实际积压工作值的标准预配。返回值:
功能:listen函数使用主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。

调用listen导致套接字从CLOSED状态转换到LISTEN状态。

backlog:
Linux内核协议栈为一个tcp连接管理使用两个队列,分别是:
半连接队列:用来保存SYN_SENT和SYN_RECV两个状态的连接,也就是三次握手还没有完成,连接还没建立的连接。
全连接队列:用来保存保存ESTABLISHED状态的连接。三次握手已经完成的连接。
全连接队列存放三次握手成功的连接。如果当服务器不调用accept函数,没有将全连接队列的请求拿出来,当该队列满的时候,客户端的连接就无法再过来,而是存放在半连接队列中,所以当全连接满时,服务器会处于SYN_RECV状态。

注意:全队列的长度是listen的第二个参数+1

内核在判定全连接队列为满时采用的是>而不是>=,我们listen的第二个参数为sk_max_ack_backlog表示全连接队列最大长度,只有当前全连接队列长度sk_ack_backlog比sk_max_ack_backlog大时,才判定全连接队列已满。

accept()

SOCKET WSAAPI accept(
    SOCKET s,
    struct sockaddr FAR * addr,
    int FAR * addrlen
);

参数:s:一个描述符,用于标识已使用 侦听 函数处于侦听状态的套接字。 连接实际上是使用 接受返回的套接字建立的。
addr:指向接收连接实体地址(称为通信层)的缓冲区的可选指针。 添加器参数的确切格式由创建 sockaddr结构中的套接字时建立的地址系列确定。
addrlen:指向包含 addr 参数指向的结构长度的整数的可选指针。
返回值:成功accept将返回一个类型为 SOCKET 的值,该值是新套接字的描述符。 此返回的值是实际连接的套接字的句柄。失败返回 INVALID_SOCKET
功能:允许在套接字上尝试传入连接

recv()

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

参数:s 指定接收端套接字描述符,客户端的socket,每个客户对应唯一的socket;
buf 指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据
客户端消息的存储空间,也就是字符数组
这个一般1500个字节,也是网络传输的最大单元,也就是客户端发过来的数据,是协议规定的,这个数据也是根据很多情况总结出来的最优值;
len 参数指明buf的长度,想要读取的字节个数,一般是参数2的字节数-1,把0字符串结尾留出来;
flags 一组影响此功能行为的标志,数据的读取方式,填0。
返回值:成功的话返回读出来字节数的大小
如果客户端下限了,返回0
如果没有系统没有收到客户端发送的数据,recv函数就停着等数据
执行失败返回SOCKET_ERROR,用函数WSAGetLastError()得到错误码,根据得到的错误码作相应处理

recvfrom()

recvfrom(
    SOCKET s,
    char FAR * buf,
    int len,
    int flags,
    struct sockaddr FAR * from,
    int FAR * fromlen
);

参数解释: sockfd – 接收端套接字描述;
buf – 用于接收数据的应用缓冲区地址;
len – 指明缓冲区大小;
flags – 通常为0(数据的接收有的时候不满足条件,client端并没有给server端发送数据,此时server端应该处于阻塞状态等待);
src_addr – 数据来源端的地址(IP address,Port number)(因为我作为接收端希望知道谁给我发的,发的什么).
fromlen – 作为输入时,fromlen常常设置为sizeof(struct sockaddr)(要知道发送过来的大小).
返回值:成功则返回接收到的字符数,失败则返回-1,错误原因存于errno中

注意:accept与recvfrom:都能获取对方信息:
getpeername和recvfrom,在tcp的客户端或者收发数据的accept_sock都能获取对方的信息
tcp的服务器使用accept,当客户端刚刚连接成功就能获取到客户端的信息。
recvfrom在接收数据的同时,可以获对方的信息(包括udp和tcp)

客户端

WSAstartup()

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

参数wVersionRequested是Windows Sockets API提供的调用方可使用的最高版本号。高位字节指出副版本(修正)号,低位字节指明主版本号。
lpWSAData 是指向WSADATA数据结构的指针,用来接收Windows Sockets实现的细节。
功能:使用Socket的程序在使用Socket之前必须调用WSAStartup函数。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。
返回值:int类型 成功返回0

socket

SOCKET WSAAPI socket(
  int af, //地址族规范。 地址族的可能值在Winsock2.h头文件中定义。
  int type,//新套接字的类型规范。
  int protocol//要使用的协议。
);

返回值: returns file (socket) descriptor if OK, -1 on error.(成功返回文件描述符,失败返回-1)
#define INVALID_SOCKET (SOCKET) (~0)
#define SOCKET_ERROR (~1)
参数:af:地址族规范。地址系列的可能值在 Winsock2.h 头文件中定义。
type:SOCK_STREAM(1)[TCP] SOCK_DGRAM(2)[UDP]
功能:套接字函数创建绑定到特定传输服务提供程序的套接字

bind()

int bind(int sock, struct sockaddr *addr, socklen_t addrlen);  //Linux
int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);  //Windows

参数:s:标识未绑定套接字的描述符。
addr:指向要分配给绑定套接字的本地地址的 sockaddr 结构的指针。
namelen:name 参数指向的值的长度(以字节为单位)。
返回值:returns 0 on success, or -1 on error.(成功返回0,失败返回-1)
功能绑定函数将本地地址与套接字相关联。

connect()

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

参数:s:标识未连接的套接字的描述符。
name:指向应建立连接的 sockaddr结构的指针。
namelen:名称参数指向的 sockaddr结构的长度(以字节为单位)。
返回值:成功返回零。错误返回SOCKET_ERROR,可以通过调用 WSAGetLastError来检索特定的错误代码。
功能连接函数建立与指定套接字的连接。
错误情况:1: 调用connect时内核发送一个SYN分节,若无响应则等待6s后再次发送一个,仍无响应则等待24s再发送一个,若总共等了75s后仍未收到响应则返回ETIMEDOUT错误;
2.:若对客户的SYN的响应是RST,则表示该server主机在我们指定的port上面没有进程在等待与之连接,比如server进程没执行,客户收到RST就立即返回ECONNREFUSED错误;
3:若客户发出的SYN在中间的某个路由上引发了一个“destination unreachable”(目的不可达)ICMP错误,客户主机内核保存该消息,并按1中所述的时间间隔发送SYN,在某个规定的时间(4.4BSD规定75s)仍未收到响应,则把保存的ICMP错误作为EHOSTUNREACHENETUNREACH错误返回给进程。
若connect失败则该套接字不再可用,必须关闭,我们不能对这种套接字再次调用connect函数。
在每次connect失败后,都必须close当前套接字描写叙述符并又一次调用socket。

send()

只能用于TCP,只能想连接好的固定主机发送

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

返回值:成功返回发送的字节数,错误则会返回-1;
类型:ssize_t类型在之前的read/write中描述符哦,相当于long。
参数: s:接收消息的套接字的文件描述符。
buf:要发送的消息。
len:要发送的字节数。
flags:flags参数表示下列标志中的0个或多个

MSG_CONFIRM :用来告诉链路层,
MSG_DONTROUTE:不要使用网关来发送数据,只发送到直接连接的主机上。通常只有诊断或者路由程序会使用,这只针对路由的协议族定义的,数据包的套接字没有。
MSG_DONTWAIT :启用非阻塞操作,如果操作阻塞,就返回EAGAIN或EWOULDBLOCK
MSG_EOR :当支持SOCK_SEQPACKET时,终止记录。
MSG_MORE :调用方有更多的数据要发送。这个标志与TCP或者udp套接字一起使用
MSG_NOSIGNAL :当另一端中断连接时,请求不向流定向套接字上的错误发送SIGPIPE ,EPIPE 错误仍然返回。
MSG_OOB:在支持此概念的套接字上发送带外数据(例如,SOCK_STREAM类型);底层协议还必须支持带外数据

获取IP和PORT

getsockname()

int WSAAPI getsockname(
        SOCKET   s,
  		struct sockaddr FAR * name,
  		int FAR * namelen
);

参数:s :标识套接字的描述符。
name:指向接收套接字) 地址 (名称的 SOCKADDR结构的指针。
namelen:名称缓冲区的大小(以字节为单位)。
返回值:如果未发生错误, 则 getsockname返回零。否则返回-1
功能:检索套接字的本地名称。IP和端口。(获取本地协议地址)(UDP)
注意:必须在之前绑定或者是TCP连接
1、在发送和接收之前,根据bind指定ip和port获得结果;
2、如果端口bind时指定0,可以获取到系统已分配的端口;
3、在发送和接收之前,没有执行bind就会调用失败
4、在发送和接受之后,即使没有执行bind也能获取本机信息。

getpeername()

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

参数:s :标识套接字的描述符。
name:指向接收套接字) 地址 (名称的 SOCKADDR结构的指针。
namelen:名称缓冲区的大小(以字节为单位)。
返回值:如果未发生错误, getpeername 将返回零。否则返回-1
功能:检索连接到套接字的对等方的地址。(获取外地协议地址)

注意:1、获取本机和对方的IP地址:
getpeername只能用于TCP,服务器或者客户端都可以使用;
getsockname可以用于udp和tcp:
udp如果绑定的是0IP,显示的本机ip为:0.0.0.0
getsockname用于tcp的侦听sock,如果绑定的是0IP,
显示的本机ip为:0.0.0.0
getpeername用于tcp的侦听sock,会获取失败。
getpeername用于tcp的连接成功之后的收发accept_socka,
可以显示出对方的真实IP和端口。
2、getpeername和getsockname在tcp 的客户端使用:
在建立连接之前
getsockname获取本机信息:0.0.0.0-55500
getpeername获取不到对方信息!
在建立连接之后可以获取本机和对方的地址信息:
对方信息:192.168.193.1 - 8899
本机信息:192.168.193.1 - 55500

cp:
udp如果绑定的是0IP,显示的本机ip为:0.0.0.0
getsockname用于tcp的侦听sock,如果绑定的是0IP,
显示的本机ip为:0.0.0.0
getpeername用于tcp的侦听sock,会获取失败。
getpeername用于tcp的连接成功之后的收发accept_socka,
可以显示出对方的真实IP和端口。
2、getpeername和getsockname在tcp 的客户端使用:
在建立连接之前
getsockname获取本机信息:0.0.0.0-55500
getpeername获取不到对方信息!
在建立连接之后可以获取本机和对方的地址信息:
对方信息:192.168.193.1 - 8899
本机信息:192.168.193.1 - 55500

案例

Server
#include <WinSock2.h>
#include <iostream>
using namespace std;
enum {PORT = 8899};
#pragma comment(lib,"ws2_32.lib")
int main()
{
	WSADATA w;
	if (WSAStartup(MAKEWORD(2, 3), &w))
	{
		cout << "初始化socket错误:" << WSAGetLastError() << endl;
		return -1;
	}

	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock == INVALID_SOCKET)
	{
		cout << "创建socket错误:" << WSAGetLastError() << endl;
		return -1;
	}

	sockaddr_in addr_in= { AF_INET ,htons(PORT)};
	int n = bind(sock, (const sockaddr*)&addr_in, sizeof(addr_in));
	if (n)
	{
		cout << "绑定失败:" << WSAGetLastError() << endl;
		return -1;
	}

	listen(sock,5);//积压数目5
	char s[256];
	//用来显示客户端连接者的IP和PORT  这个socket用来跟客户端收发数据
	//后面两个参数类似与recvfrom的后两个参数
	SOCKET accept_sock;
	while ((accept_sock = accept(sock, nullptr, nullptr)) != INVALID_SOCKET)
	{
		while (true)
		{
			n = recv(accept_sock, s, sizeof(s), 0);
			if (n <= 0 || n >= sizeof(s))
			{
				break;
			}
			s[n] = 0;
			cout << s << endl;
		}
		
	}
	closesocket(sock);
	return 0;
}
Client
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <iostream>
enum {PORT = 8899};
#pragma comment(lib,"ws2_32.lib")
using namespace std;

int main()
{
	WSADATA w;
	if (WSAStartup(MAKEWORD(2, 2), &w))
	{
		cout << "初始化socket错误:" << WSAGetLastError() << endl;
		return -1;
	}
	SOCKET sock = socket(AF_INET, SOCK_STREAM,0);
	if (sock == INVALID_SOCKET)
	{
		cout << "创建socket错误:" << WSAGetLastError() << endl;
		return -1;
	}

	sockaddr_in addr_in = { AF_INET };//0端口系统自动分配
	int n = bind(sock, (const sockaddr*)&addr_in, sizeof(addr_in));
	if (n)
	{
		cout << "绑定失败:" << WSAGetLastError() << endl;
		return -1;
	}
	int len = sizeof(addr_in);
	n = getsockname(sock, (sockaddr*)&addr_in, &len);
	if (!n)
	{
		cout << "本机信息:" << inet_ntoa(addr_in.sin_addr) << " - " << htons(addr_in.sin_port) <<endl;
	}
	n = getpeername(sock, (sockaddr*)&addr_in, &len);
	if (n)
	{
		cout << "在建立连接之前,获取不到对方信息!"<<endl;
	}
	else
	{
		cout << "对方信息:" << inet_ntoa(addr_in.sin_addr) << " - " << htons(addr_in.sin_port) << ":";
	}

	addr_in.sin_port = htons(PORT);
	addr_in.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	n = connect(sock, (const sockaddr*)&addr_in, sizeof(addr_in));
	if (n)
	{
		cout << "连接失败:" << WSAGetLastError() << endl;
		return -1;
	}

	if (n)
	{
		cout << "在建立连接之前,获取不到对方信息!";
	}
	else
	{
		cout << "对方信息:" << inet_ntoa(addr_in.sin_addr) << " - " << htons(addr_in.sin_port) << endl;
	}
	n = getsockname(sock, (sockaddr*)&addr_in, &len);
	if (!n)
	{
		cout << "本机信息:" << inet_ntoa(addr_in.sin_addr) << " - " << htons(addr_in.sin_port) << ":";
	}

	char s[256];
	while (true)
	{
		cin >> s;
		send(sock, s, strlen(s), 0);
	}
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值