对于套接字的简单理解:
可认为一个IP与一端口(port)联合在一起形成一个套接字,它是网络上的一个传输接口。在网络的另外一端可有一个对应的套接字与通信。
TCP/IP网络应用中,最常用的通信模式是客户/服务器模式( Client/Server model),即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务.
客户端与服务器的连接方式主要有两种:
1.流式套接口(TCP)
流式套接口是可靠的双向通讯的数据流。传送的包会按发送时的顺序到达int s=socket(PF_ INET, SOCK_ STREAM,IPPROTO _
TCP)
2.数据报套接口(UDP)
使用这种方式,传送的包不一定会按发送时的顺序到达int s=socket(PF INET, SOCK DGRAM, IPPROTO UDP
从上面所描述过程可知:
1.客户与服务器进程的作用是非对称的,它们各自完成的功能不同,因此编码也不同。
2.服务进程一般是先于客户请求而启动的,启动后即在相应的 Socket监听来自客户端的请求。只要系统运行,该服务进程一直存在,直到正常或强迫终止( daemon)。
服务器方面初始时需要执行的操作:
int socket() //建立一个 Socket
int bind() //与某个端口绑定
int listen() //开始监听端口
int accept() //等待/接受客户端的连接请求
客户端需要执行的操作:
int socket() //建立一个 Socket
int connect() //连接到服务器
环境:Windows10
工具:visual studio 2019
包含头文件:#include
附加:(显示引用dll)#pragma comment(lib,“ws2_32.lib”)
过程:[右键]项目->属性->链接器->输入->附加依赖项->编辑->添加ws2_32.lib,取消勾选"从父级或项目默认设置继承"
服务端cpp文件
/*
Socket服务器端代码
服务器监听端口8888
建立连接后,接收Client传输的文件路径
若文件路径存在,则发送该文件给Client,发送完毕则关闭连接。
*/
#include
#include
#include
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
#define BUFFER_SIZE 2048
#define FILE_NAME_MAX_SIZE 512
int main(int argc, char* argv[])
{
/*
初始化WSA,使得程序可以调用windows socket
*/
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(sockVersion, &wsaData) != 0)
{
return 0;
}
/*
创建监听用套接字,server_socket
类型是TCP
并检测是否创建成功
*/
SOCKET server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (server_socket == INVALID_SOCKET) {
//如果创建的socket无效,则结束程序
perror("socket error !\n");
return 0;
}
/*
创建地址,server_addr,并设置端口和IP
*/
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
//端口号 8888
server_addr.sin_port = htons(8888);
//INADDR_ANY表示本机任意IP地址
server_addr.sin_addr.S_un.S_addr = INADDR_ANY;
//将socket与地址server_addr绑定
if (bind(server_socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
perror("bind error !\n");
return 0;
}
//server_socket开始监听
if (listen(server_socket, 20) == SOCKET_ERROR)
{
perror("listen error !\n");
return 0;
}
while (1)
{
printf("等待连接...\n");
// 定义客户端的socket和socket地址结构
SOCKET client_socket;
sockaddr_in client_addr;
int client_addr_length = sizeof(client_addr);
// 接受连接请求,返回一个新的socket(描述符),这个新socket用于同连接的客户端通信
// accept函数会把连接到的客户端信息写到client_addr中
client_socket = accept(server_socket, (SOCKADDR *)&client_addr, &client_addr_length);
if (client_socket == INVALID_SOCKET)
{
perror("Socket连接建立失败:\n");
continue;
}
char IP_BUFFER[256];
memset(IP_BUFFER, 0, 256);
InetNtop(AF_INET, &client_addr.sin_addr, IP_BUFFER,256);
printf("Socket连接建立,客户端IP为:%s,端口为:%d\n", IP_BUFFER, ntohs(client_addr.sin_port));
//接收客户端请求的的文件路径
// recv函数接收数据到缓冲区buffer中
char buffer[BUFFER_SIZE];
memset(buffer,0, BUFFER_SIZE);
if (recv(client_socket, buffer, BUFFER_SIZE, 0) < 0)
{
perror("接收文件名失败:\n");
break;
}
// 然后从buffer(缓冲区)拷贝到file_name中
char file_name[FILE_NAME_MAX_SIZE + 1];
memset(file_name, 0,FILE_NAME_MAX_SIZE + 1);
strncpy_s(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE ? FILE_NAME_MAX_SIZE : strlen(buffer));
// 打开文件并读取文件数据
FILE *fp;
errno_t F_ERR= fopen_s(&fp,file_name, "rb");
if (F_ERR != 0)
{
printf("文件打开失败:%s\n", file_name);
}
else
{
printf("开始传输文件:%s\n", file_name);
memset(buffer,0, BUFFER_SIZE);
int length = 0;
// 每读取一段数据,便将其发送给客户端,循环直到文件读完为止
while ((length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0)
{
if (send(client_socket, buffer, length, 0) < 0)
{
printf("文件发送失败:%s/n", file_name);
break;
}
memset(buffer,0, BUFFER_SIZE);
}
// 关闭文件
fclose(fp);
printf("文件传输完成:%s!\n", file_name);
}
// 关闭与客户端的连接
closesocket(client_socket);
}
// 关闭监听用的socket
closesocket(server_socket);
WSACleanup();
return 0;
}
客户端cpp
/*
Socket客户端代码
服务器127.0.0.1通信,端口8888
建立连接后,发送给服务器,需要传输的文件路径
若文件路径存在,接收服务器发送的文件流,发送完毕则关闭连接。
*/
#include
#include
#include
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
#define BUFFER_SIZE 2048
#define FILE_NAME_MAX_SIZE 512
int main(int argc, char* argv[])
{
/*
初始化WSA,使得程序可以调用windows socket
*/
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(sockVersion, &wsaData) != 0)
{
return 0;
}
/*
创建监听用套接字,server_socket
并检测是否创建成功
*/
SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); ;
if (client_socket == INVALID_SOCKET) {
//如果创建的socket无效,则结束程序
perror("socket error !");
return 0;
}
/*
创建地址结构,server_addr,并设置端口和IP
*/
sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
//要连接的服务器端口号 8888
server_addr.sin_port = htons(8888);
//指定服务器的地址127.0.0.1
InetPton(AF_INET, "127.0.0.1",&server_addr.sin_addr.s_addr);
//与地址server_addr建立连接
if (connect(client_socket, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)))
{
perror("connect error !\n");
return 0;
}
char REMOTE_file_name[FILE_NAME_MAX_SIZE + 1];
memset(REMOTE_file_name, 0, FILE_NAME_MAX_SIZE + 1);
printf("请输入要获取的服务器文件路径:\n");
scanf_s("%s", REMOTE_file_name, FILE_NAME_MAX_SIZE);
char LOCAL_file_name[FILE_NAME_MAX_SIZE + 1];
memset(LOCAL_file_name, 0, FILE_NAME_MAX_SIZE + 1);
printf("请输入保存文件的本地路径:\n");
scanf_s("%s", LOCAL_file_name, FILE_NAME_MAX_SIZE);
char buffer[BUFFER_SIZE];
memset(buffer,0 , BUFFER_SIZE);
strncpy_s(buffer, REMOTE_file_name, strlen(REMOTE_file_name)>BUFFER_SIZE ? BUFFER_SIZE : strlen(REMOTE_file_name));
// 向服务器发送buffer中的数据
if (send(client_socket, buffer, BUFFER_SIZE, 0) < 0)
{
perror("发送文件名失败:");
exit(1);
}
// 打开文件,准备写入
FILE *fp;
errno_t F_ERR = fopen_s(&fp, LOCAL_file_name, "wb");
if (F_ERR != 0)
{
printf("文件打开失败:%s\n", LOCAL_file_name);
exit(1);
}
// 从服务器接收数据到buffer中
// 每接收一段数据,便将其写入文件中,循环直到文件接收完并写完为止
memset(buffer,0, BUFFER_SIZE);
int length = 0;
while ((length = recv(client_socket, buffer, BUFFER_SIZE, 0)) > 0)
{
if (fwrite(buffer, sizeof(char), length, fp) < length)
{
printf("文件写入失败:%s\n", LOCAL_file_name);
break;
}
memset(buffer, 0, BUFFER_SIZE);
}
printf("\n成功从服务器接收文件\n存入本地目录:%s\n", REMOTE_file_name, LOCAL_file_name);
// 接收成功后,关闭文件,关闭socket、WSA
fclose(fp);
closesocket(client_socket);
WSACleanup();
system("pause");
return 0;
}
源码援引自袁华老师MOOC计算机网络中的github
VS2019显示报错:
这属于Windows编程过程中遇到字符串转换的问题
有ANSI编码和UNICODE编码之分。
1.ANSI字符集,它们正式的名称应该是多字节字符系统(Multi-Byte Chactacter System,即MBCS)。ANSI(使用"")中的字符采用8bit.对于字符来说ANSI以单字节存放英文字符,以双字节存放中文等字符.8bit的ANSI编码只能表示256种字符,表示26个英文字母是绰绰有余的,但是表示汉字,韩国语等有着成千上万个字符的非西方字符肯定就不够了,正是如此才引入了UNICODE标准。
2.Unicode码也是一种国际标准编码,采用二个字节编码,与ANSI码不兼容,且Unicode,英文和中文的字符都以双字节存放
因为Windows支持两种字符串,这样对应的就有了两套字符串处理函数,比如:strlen和wcslen,分别用于处理两种字符串.
ANSI:即char,可用字符串处理函数:strcat( ),strcpy( ), strlen( )等 以str打头的函数。 char :单字节变量类型,最多表示256个字符.
UNICODE:即wchar_t 可用字符串处理函数:wcscat(),wcscpy(),wcslen()等 以wcs打头的函数。 宽字节变量类型(即:unsigned short类型),用于表示Unicode
改char_t为wchar_t
报错解决后,我们开始尝试传输文件