不只是winsock,几乎所有网络程序可以分为5个步骤:
1. 打开socket
2. 命名socket
3. 与另一个socket建立关联
4. 与socket之间发送和接收数据
5. 关闭socket
(一)下面主要说明连接的建立过程。
打开socket:int socket(intaf, int type, int protocol);
参数说明:
af: 地址族的类型AF_UNIXAF_INET
type: 数据格式:流SOCK_STREAM/报文SOCK_DGRAM
protocol: 协议类型
命名socket:intbind(SOCKET s, struct sockaddr FAR *addr, int namelen);
参数说明:
s:socket句柄。
addr:指向socket地址结构的指针。
namelen:addr所指向的socket结构的长度。
这里用到的一个数据结构sockaddr_in是给socket命名的关键,具体如下:
struct sockaddr_in{
short sin_family;//地址族
u_short sin_port;//端口号
struct in_addr sin_addr;//地址
char sin_zero[8];//没用
};//这里当然都是绑定本机地址127.0.0.1
第1、2步是服务器和客户端都要做的,但是一般情况下客户端为了防止端口号冲突会采用隐式命名的方式,在向服务器发起连接的时候(调用connect()函数)协议栈会分配给客户端独有的端口号。
3.1.服务器监听客户端
int PASCAL FAR listen(SOCKETs, int backlog);
3.2.客户端发起连接
int PASCAL FAR connet(SOCKETs, struct sokaddr FAR *addr, int namelen);
3.3.服务器完成连接
从监听socket上接受一个等待的连接请求后,accept()函数为新创建的连接返回一个新的socket。
SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR *addr, int FAR*addrlen);
(二)接下来就是发送数据和关闭套接字了。
这里我实现了两个功能:发送消息和发送文件。
客户端首先选择发送类型:1.消息;2.文件
两种方式实现起来没有大差别,只是在发送文件的时候首先发送一个标志FILE_FLAG " xmzulesile "提醒服务器将要发送文件,然后服务器将接收到的字节流写入文件,客户端发送文件结束后发送一个标志FILE_SEND_OVER "over",服务器收到后知道发送结束给客户端发送”Received!”。
最后说明一个问题:这里可以说使用了停-等协议,发送一个数据包收到确认再发送下一个数据包,否则客户端连续发送完所有数据包(包括结束标志),服务器收不到结束标志,就会一直阻塞下去。具体原因还没想明白,一开始没有加"接收确认"的时候,调试是没问题的,但是运行就会阻塞。
上面就是工作流程,结束后就可以关闭套接字了。
附上源代码:
客户端:
#include <stdio.h>
#include <winsock2.h>
#include <tchar.h>
#pragma comment(lib, "ws2_32.lib") //加载库文件
char FILE_FLAG[12] = "xmzulesile"; // 发送文件的标志
char FILE_SEND_OVER[10] = "over"; // 发送文件结束的标志
int main()
{
// 初始化Winsock
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR)
{
printf_s("failed to initialize!\n");
return 0;
}
// 建立socket
SOCKET m_socketServer;// 连接服务器的套接字
m_socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_socketServer == INVALID_SOCKET)
{
printf_s("failed to create client socket!\n");
WSACleanup();
return 0;
}
sockaddr_in clientAddr;
clientAddr.sin_family = AF_INET;
clientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
clientAddr.sin_port = htons(3579);
// 连接到服务器
if (connect(m_socketServer, (SOCKADDR*)&clientAddr, sizeof(clientAddr)) == SOCKET_ERROR)
{
printf_s("failed to connect\n");
closesocket(m_socketServer);
WSACleanup();
return 0;
}
char c;
char buf[1024 + 1] = { 0 };
char filename[255];
FILE *fp = NULL;
//send(m_socketServer, buf, strlen(buf), 0);
// 发送数据
while (TRUE)
{
printf_s("发送:1.消息;2.文件\n");
if ((c = getchar()) == '1')
{
getchar();// 丢弃回车符
gets_s(buf, 1024);// 读取消息
send(m_socketServer, buf, strlen(buf), 0);
}
else if (c == '2')
{
getchar();// 丢弃回车符
printf_s("请输入本地文件路径: ");
scanf_s("%s",&filename, 255);
getchar();// 丢弃回车符
if (fopen_s(&fp, filename, "rb+"))
{
::MessageBox(NULL, _T("failed to open file in client"), NULL, 0);
return 0;
}
send(m_socketServer, FILE_FLAG, sizeof(FILE_FLAG), 0);//通知服务器将要发送文件
// 读取文件
int i = fread(buf, 1, 1024, fp);
while (i > 0)
{
//这里可以说使用停-等协议,发送一个数据包收到确认再发送下一个数据包
//否则客户端连续发送完所有数据包(包括结束标志),服务器收不到结束标志,就会一直阻塞下去
//具体原因还没想明白,一开始没有加"接收确认"的时候,调试是没问题的,但是运行就会阻塞
send(m_socketServer, buf, i, 0);
recv(m_socketServer, buf, 1024, 0);//接收确认
SecureZeroMemory(buf, sizeof(buf));
i = fread(buf, 1, 1024, fp);
}
if (ferror(fp))
{
MessageBox(NULL, _T("fread error"), NULL, 0);
return 0;
}
//告诉服务器发送完毕
send(m_socketServer, FILE_SEND_OVER, sizeof(FILE_SEND_OVER), 0);
//接收通知(服务器接收完毕)
int ret = recv(m_socketServer, buf, 1024, 0);
if (ret == 0)
{
printf("Recv data error: %d\n", WSAGetLastError());
return 0;
}
if (buf[0])//输出服务器发过来的消息
{
buf[ret] = 0;
printf_s("Server:%s\n", buf);
}
if (fp)
{
fclose(fp);//关闭文件
fp = NULL;
}
}
else
{
getchar();
printf_s("incorrect input\n");
}
}
closesocket(m_socketServer);
WSACleanup();
return 0;
}
#include <WinSock2.h>
#include <iostream>
#include <string.h>
#include <tchar.h>
#pragma comment(lib, "ws2_32.lib")
char FILE_FLAG[12] = "xmzulesile"; // 客户端发来文件的标志
char FILE_SEND_OVER[10] = "over"; // 客户端发送文件结束的标志
//接收文件
unsigned recvFile(SOCKET s, const char *filename)
{
FILE *fp;
char buf[1024] = { 0 };
int ret = 0;
if (fopen_s(&fp, filename, "wb+"))
{
::MessageBox(NULL, _T("failed to write file in server"), NULL, 0);
return 0;
}
do
{
//接收数据
ret = recv(s, buf, 1024, 0);
if (ret == 0)
{
printf("Recv data error: %d\n", WSAGetLastError());
return 0;
}
//是否接收结束
if (!strcmp(buf, FILE_SEND_OVER))
{
send(s, "Received!", 10, 0);//通知发送端全部接收完毕
break;
}
else
{
//把数据写入打开的文件
fwrite(buf, 1, ret, fp);
send(s, "Got it", 10, 0);//发送确认
SecureZeroMemory(buf, sizeof(buf));
}
} while (ret > 0);
if (fp)
{
fclose(fp);
fp = NULL;
}
return 0;
}
int main()
{
WSADATA wsaData;
int iInit = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iInit != NO_ERROR)
{
printf_s("failed to initialize!\n");
return 0;
}
SOCKET m_socketListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_socketListen == INVALID_SOCKET)
{
printf_s("failed to create server socket!\n");
WSACleanup();
return 0;
}
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
serverAddr.sin_port = htons(3579);
//bind
if (bind(m_socketListen, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
printf_s("failed to bind!\n");
closesocket(m_socketListen);
WSACleanup();
return 0;
}
//listen
if (listen(m_socketListen, 1) == SOCKET_ERROR) {
printf_s("listen failed\n");
closesocket(m_socketListen);
WSACleanup();
return 0;
}
//accept
//从监听socket上接受一个等待的连接请求后,accept()函数为新创建的连接返回一个新的socket
SOCKET m_socketClient = accept(m_socketListen, NULL, NULL);// 跟客户端连接的套接字
if (m_socketClient == INVALID_SOCKET) {
wprintf(L"accept failed with error: %ld\n", WSAGetLastError());
closesocket(m_socketListen);
WSACleanup();
return 0;
}
int bytes = SOCKET_ERROR;
char buf[1024] = { 0 };
//接收数据
while (TRUE)
{
if((bytes = recv(m_socketClient, buf, 1024, 0)) == 0)
{
break;
}
if (!strcmp(FILE_FLAG, buf))
{
printf_s("Here comes a file\n");
recvFile(m_socketClient, "d:\\recv");
}
else
{
buf[bytes] = '\0';// 字符串结尾标志
printf_s("Client:%s\n", buf);
}
}
closesocket(m_socketListen);
WSACleanup();
return 0;
}