简单TCP通信实验
目录
实验设备:
目标系统:windows
软件工具:vs2022/VC6/dev
实验要求:
- 完成TCP服务端和客户端的程序编写;
- 实现简单字符串的收发功能。
- 需附上代码及运行结果截图。
实验内容:
分析:
1、套接字类型
① 流式套接字(SOCK_STREAM): 提供面向连接的、可靠的字节流服务,用于TCP。
② 数据报套接字(SOCK_DGRAM): 提供无连接的,不可靠的数据报服务,用于UDP。
③ 原始套接字(SOCK_RAW): 允许对较低层的协议,如IP、ICMP直接访问。
复习TCP三次握手过程,理解TCP socket编程。
2、socket编程步骤
服务器端:
创建socket----socket()
绑定的socket和端口号------bind()
监听该端口号----listen()
接收来自客户端的连接请求---accept()
从socket中读取字符----recv()
关闭socket---close()
客户端:
创建socket-----socket()
连接指定计算机的端口-----connect()
向socket中写入信息-----send()
关闭socket-----close()
3、socket编程实现具体思路
服务器端:
其过程是首先服务器方要先启动,并根据请求提供相应服务
1)打开一通信通道并告知本地主机,它愿意在某一公认地址上的某端口接收客户请求;
2)等待客户请求到达该端口;
3)接收到客户端的服务请求时,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。
4)返回第(2)步,等待另一客户请求。
5)关闭服务器
客户端:
- 打开一通信通道,并连接到服务器所在主机的特定端口;
- 向服务器发服务请求报文,等待并接收应答;继续提出请求…
- 请求结束后关闭通信通道并终止。
- 代码实现过程分析
1)、创建套接字----socket()
应用程序在使用套接字前,首先必须拥有一个套接字,系统调用socket()向应用程序提供创建套接字的手段,其调用格式如下:
SOCKET PASCAL FAR socket(int af, int type, int protocol)
该调用要接收三个参数:af、type、protocol。
Af--------指定通信发生的区域:AF_UNIX、AF_INET、AF_NS等,而DOS、WINDOWS中仅支持AF_INET,它是网际网区域。因此,地址族与协议族相同。
Type----- 描述要建立的套接字的类型。这里分三种:
一、是TCP流式套接字(SOCK_STREAM)提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复地发送,且按发送顺序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。文件传送协议(FTP)即使用流式套接字。
二、是数据报式套接字(SOCK_DGRAM)提供了一个无连接服务。数据包以独立包形式被发送,不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。网络文件系统(NFS)使用数据报式套接字。
三、是原始式套接字(SOCK_RAW)该接口允许对较低层协议,如IP、ICMP直接访问。常用于检验新的协议实现或访问现有服务中配置的新设备。
Protocol-----说明该套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式。
2)指定本地地址---bind()
当一个套接字用socket()创建后,存在一个名字空间(地址族),但它没有被命名。bind()将套接字地址(包括本地主机地址和本地端口地址)与所创建的套接字号联系起来,即将名字赋予套接字,以指定本地半相关。其调用格式如下:
int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR * name, int namelen);
s----是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
name-----是赋给套接字s的本地地址(名字),其长度可变,结构随通信域的不同而不同。
namelen-----表明了name的长度。如果没有错误发生,bind()返回0。否则返回SOCKET_ERROR。
3)建立套接字连接---connect()与accept()
这两个系统调用用于完成一个完整相关的建立,其中connect()用于建立连接。accept()用于使服务器等待来自某客户进程的实际连接。connect()的调用格式如下:
int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR * name, int namelen);
参数s是欲建立连接的本地套接字描述符。
参数name指出说明对方套接字地址结构的指针。
对方套接字地址长度由namelen说明。
如果没有错误发生,connect()返回0。否则返回值SOCKET_ERROR。在面向连接的协议中,该调用导致本地系统和外部系统之间连接实际建立。、
accept()的调用格式如下:
SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);
参数s为本地套接字描述符,在用做accept()调用的参数前应该先调用过listen()。
addr指向客户方套接字地址结构的指针,用来接收连接实体的地址。addr的确切格式由套接字创建时建立的地址族决定。
addrlen为客户方套接字地址的长度(字节数)。
如果没有错误发生,accept()返回一个SOCKET类型的值,表示接收到的套接字的描述符。否则返回值INVALID_SOCKET。
4)监听连接---listen()
此调用用于面向连接服务器,表明它愿意接收连接。listen()需在accept()之前调用,其调用格式如下:
int PASCAL FAR listen(SOCKET s, int backlog);
参数s标识一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求。
backlog表示请求连接队列的最大长度,用于限制排队请求的个数,目前允许的最大值为5。
如果没有错误发生,listen()返回0。否则它返回SOCKET_ERROR。
listen()在执行调用过程中可为没有调用过bind()的套接字s完成所必须的连接,并建立长度为backlog的请求连接队列。
5)数据传输---send()与recv()
当一个连接建立以后,就可以传输数据了。常用的系统调用有send()和recv()。
send()调用用于s指定的已连接的数据报或流套接字上发送输出数据,格式如下:
int PASCAL FAR send(SOCKET s, const char FAR *buf, int len, int flags);
参数s为已连接的本地套接字描述符。
buf 指向存有发送数据的缓冲区的指针,其长度由len 指定。
flags指定传输控制方式,如是否发送带外数据等。如果没有错误发生,
send()返回总共发送的字节数。否则它返回SOCKET_ERROR。
recv()调用用于s指定的已连接的数据报或流套接字上接收输入数据,格式如下:
int PASCAL FAR recv(SOCKET s, char FAR *buf, int len, int flags);
参数s 为已连接的套接字描述符。
buf指向接收输入数据缓冲区的指针,其长度由len 指定。
flags指定传输控制方式,如是否接收带外数据等。
如果没有错误发生,recv()返回总共接收的字节数。如果连接被关闭,返回0。否则它返回SOCKET_ERROR。
6)关闭套接字---closesocket()
closesocket()关闭套接字s,并释放分配给该套接字的资源;如果s涉及一个打开的TCP连接,则该连接被释放。closesocket()的调用格式如下:
BOOL PASCAL FAR closesocket(SOCKET s);
参数s待关闭的套接字描述符。
如果没有错误发生,closesocket()返回0。否则返回值SOCKET_ERROR。
注意:client_in.sin_addr.S_un.S_addr = inet_addr()
inet_addr()内地址要查询本机的ip。或者直接用回环地址127.0.0.1
实验结果截图:
程序代码:
客户端:
#include
#include
#pragma comment(lib, "Ws2_32.lib")
#include
int main()
{
char sendBuf[1024];
char receiveBuf[1024];
while (1)
{
WSADATA wsadata;
if (0 == WSAStartup(MAKEWORD(2, 2), &wsadata))
{
printf("等待连接....\n");
}
else
{
printf("连接失败!\n");
}
SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN client_in;
client_in.sin_addr.S_un.S_addr = inet_addr("172.29.18.16");//将网络地址字符串转换成二进制形式
client_in.sin_family = AF_INET;
client_in.sin_port = htons(6000);
connect(clientSocket, (SOCKADDR*)&client_in, sizeof(SOCKADDR));
recv(clientSocket, receiveBuf, 1024, 0);
printf("收到来自服务器: %s\n", receiveBuf);
printf("向服务器发出: ");
gets(sendBuf);
send(clientSocket, sendBuf, 1024, 0);
closesocket(clientSocket);
WSACleanup();
}
return 0;
}
服务端:
#include
#include
#pragma comment(lib, "Ws2_32.lib")
#include
int main()
{
char sendBuf[1024];
char receiveBuf[1024];
while (1)
{
//创建套接字,socket前的一些检查工作.
//服务的启动
WSADATA wsadata;//wsa 即windows socket async 异步套接字
if (0 != WSAStartup(MAKEWORD(2, 2), &wsadata))
{
printf("未连接\n");
return 0;
}
else
{
printf("连接成功!\n");
}
SOCKET serSocket = socket(AF_INET, SOCK_STREAM, 0);//创建可识别的套接字//parm1: af 地址协议族 ipv4 ipv6
//parm2:type 传输协议类型 流式套接字(SOCK_STREAM),数据包套接字(SOCK_DGRAM)
//parm3:ptotoc1 使用具体的某个传输协议
SOCKADDR_IN addr; //需要绑定的参数,主要是本地的socket的一些信息。
addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //ip地址,htonl即host本机 to:to n:net l:unsigned long 大端存储,低字节在高位
addr.sin_family = AF_INET;
addr.sin_port = htons(6000); //端口 htons将无符号短整型转化为网络字节序
bind(serSocket, (SOCKADDR*)&addr, sizeof(SOCKADDR));
listen(serSocket, 5);
SOCKADDR_IN clientsocket;
int len = sizeof(SOCKADDR);
SOCKET serConn = accept(serSocket, (SOCKADDR*)&clientsocket, &len);
printf("向客户端发出: \n");
gets(sendBuf);
send(serConn, sendBuf, 1024, 0);
recv(serConn, receiveBuf,1024, 0);
printf("收到来自客户端: %s\n", receiveBuf);
closesocket(serConn);//关闭
WSACleanup();//释放资源
}
return 0;
}
来源地址:https://blog.csdn.net/qq_51782199/article/details/129528327