服务器端:
1>建立一个基于对话框的应用程序StreamSocketServer
2>设计界面,将“确定”按钮的标题改为“启动”
3>双击“启动按钮”,添加以下代码,初始化网络,启动服务器监听
void CStreamSocketServerDlg::OnOK()
{
// TODO: Add extra validation here
WSADATA wsaData;
/*
typedef struct WSAData
{
WORD wVersion; //为希望使用的Winsock版本
WORD wHeighVersion; //返回现有的Winsock库的最高版本
char szDescription[WSADESCRIPTION_LEN+1]; //通常不用
char szSystemStatus[WSASYS_STATUE_LEN+1]; //通常不用
unsigned short iMaxSockets; //可同时打开的套接字数
unsigend short iMaxUdpDg; //数据报最大长度
char FAR * lpVendorInfo; //为制定Winsock实施方案的厂商信息所预留,在任何一个Win32平
台上都没有使用这个字段]
}
*/
//初始化TCP协议
BOOL ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
/*
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
wVersionRequested 用户制定准备加载的Winsock库的版本.
宏定义MAKEWOARD(X, Y) 获得wVersionRequested的正确值, [X为高位字节,Y为低位字节,
lpWSAData 为指向LPWSDATA结构的指针,包含了加载库版本有关的信息
*/
if(ret != 0)
{
MessageBox("初始化网络协议失败!");
return;
}
//创建服务器端套接字
ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
/*
SOCKET socket(int af, int type, int protocol);
af用于制定网络类型,一般取AF_INET,表示该套接字在Internet域中进行通信
type用于指定套接字类型,SOCK_STREAM表示流套接字, SOCK_DGRAM表示数据报套接字
protocol用于指定网络协议,默认为0,表示TCP/IP协议
*/
if(ServerSock == INVALID_SOCKET)
{
MessageBox("创建失败!");
closesocket(ServerSock);
}
//绑定到本地一个端口上
sockaddr_in localaddr;
/*
struct sockaddr_in
{
short sin_family; //必须为AF_INET,表示该socket处于Internet域
u_short sin_port; //用于指定服务端口
struct in_addr sin_addr; //把一个IP地址保存为一个4字节的数,其数据类型为无符号长整数
类型
char sin_zero[8]; //只充当填充项角色,使得与sockaddr长度保持一致
}
*/
localaddr.sin_family = AF_INET;
localaddr.sin_port = htons(6000);
/*
htons的功能:将一个无符号短整型的主机数值转换为网络字节顺序,即大尾顺序(big-endian)
参数u_short hostshort:16位无符号整数
返回值:TCP/IP网络字节顺序.
*/
localaddr.sin_addr.s_addr = 0;
if(bind(ServerSock, (struct sockaddr *)&localaddr, sizeof(sockaddr)) == SOCKET_ERROR)
{
MessageBox("绑定地址失败!");
closesocket(ServerSock);
WSACleanup();
return;
}
/*
int bind(SOCKET s, const struct sockaddr FAR *name, int namelen);
s标识一个未捆绑套接字的句柄,用来等待客户机的连接
name是赋予套接字的地址
*/
//将ServerSock设置为异步非阻塞模式,并为它注册各种网络异步事件
//m_hWnd为应用程序的主对话框或主窗口的句柄
if(WSAAsyncSelect(ServerSock, m_hWnd, NETWORK_EVENT, FD_ACCEPT|FD_CLOSE|FD_READ|FD_WRITE) ==
SOCKET_ERROR)
{
MessageBox("注册失败!");
WSACleanup();
return;
}
/*
int WSAAsyncSelect(
SOCKET s,
HWND hWnd, //接收消息的窗口句柄
unsigned int wMsg; //网络事件发生时要接收的消息
long lEvent; //被注册的网络事件
);
lEvent参数值及意义:
FD_READ 希望在套接字s收到数据时收到消息
FD_WRITE 希望在套接字s可以发送数据时收到消息
FD_ACCEPT 希望在套接字s收到连接请求是收到消息
FD_CONNECT 希望在套接字s连接成功是收到消息
FD_CLOSE 希望在套接字s连接关闭是收到消息
FD_OOB 希望在套接字s收到外带数据时收到消息
NETWORK_EVENT 为自定义消息,并添加其响应处理函数OnNetEvent(WPARAM wParam, LPRAM lParam)
*/
listen(ServerSock, 31); //设置为侦听模式,最多要31个连接
MessageBox("服务启动成功!");
CDialog::OnOK();
}
4>注册自定义消息NETWORK_EVENT, 并添加其相应处理函数OnNetEvent
在StreamSocketServerDlg.h中添加成员函数:
void OnNetEvent(WPARAM wParam, LPARAM lParam);
在StreamSocketServerDlg.cpp中实现:
void CStreamSocketServerDlg::OnNetEvent(WPARAM wParam, LPARAM lParam)
{
int iEvent = WSAGETSELECTEVENT(lParam); //调用Winsock API函数,得到网络事件类型
SOCKET CurSock = (SOCKET)wParam; //调用Winsock API函数,得到此事件的客户端套接字
switch(iEvent)
{
case FD_ACCEPT: //客户端连接请求事件
TRACE("Get client call!\n");
OnAccept(CurSock);
break;
case FD_CLOSE: //客户端断开事件
OnClose(CurSock);
break;
case FD_READ: //客户端网络包到达事件
OnReceive(CurSock);
break;
case FD_WRITE: //发送网络数据事件
break;
default: break;
}
}
5>添加客户端的连接请求处理函数
void CStreamSocketServerDlg::OnAccept(SOCKET CurSock)
{
SOCKET TempSock;
struct socaaddr_in ConSocketAddr;
int addrlen;
addrlen = sizeof(ConSocketAddr);
TempSock = accept(CurSock, (struct sockaddr*)&ConSocketAddr, &addrlen);
/*
用于接受连接请求
SCOKET accept(
SCOKET s, //Socket的识别码
struct sockaddr FAR * addr, //存放连接的客户端地址
int FAR* addrlen //地址长度
)
*/
if(ConnectSock == INVALID_SOCKET)
MessageBox("INVALID_SOCKET");
TRACE("建立新连接,sock:%d\n", TempSock); //输出调试信息
inet_ntoa(ConSocketAddr.sin_addr);
ConnectSock = TempSock;
char *filename = "c:\\001.doc";
if(!SendFile(filename, ConnectSock))
MessageBox("发送失败!");
}
6>添加发送数据文件函数
BOOL CStreamSocketServerDlg::SendFile(char *name, SOCKET conn)
{
char *FileName = name;
SOCKET TcpConn = conn;
CFile file;
if(!file.Open(FileName, CFile::modeRead))
{
printf("打开%s失败!\n", FileName);
return FALSE;
}
int NumBytes; //用来保存每次传送时数据块的大小
UINT Total = 0; //用来保存套接字已经传送的总的字节数
int BufSize = 1024; //发送缓冲区的大小
int Size = BufSize; //读取文件的大小
LPBYTE pBuf = new BYTE[BufSize]; //发送缓冲区
DWORD dwTemp = 0;
UINT FileLength = file.GetLength(); //得到文件的大小并发送出去
send(TcpConn, (char *)&FileLength, 4, 0);
/*
int send( //返回发送的字符总数
SOCKET s,
const char FAR *buf, //存放要传送的资料的暂存区
int len, //buf的长度
int flags //此函数被调用的方式
)
*/
file.SeekToBegin();
while(Total < FileLength)
{
if((int)(FileLength - Total) < Size)
Size = FileLength - Total;
Size = file.Read(pBuf, Size);
/*
virtual UINT Read(
void* lpBuf, //是把资源读入哪里
UINT nCount //是读入的字节数
);
*/
NumBytes = send(TcpConn, (char *)pBuf, Size, 0);
if(NumBytes == SOCKET_ERROR)
{
send(TcpConn, "ERROR", sizeof("ERROR")+1, 0);
return FALSE;
}
Total += NumBytes;
file.Seek(Total, CFile::begin);
}
delete[] pBuf;
file.Close();
closesocket(TcpConn);
return TRUE;
}
客户端:
1>新建一个基于对话框的程序StreamSocketClient
2>设计界面,添加编辑框m_CtrlPAddress输入IP地址,m_iPort用于输入端口,将“确定”按钮改为“连接”,“取消”改为“关闭”
3>双击“连接”,添加以下代码:
void CStreamSocketClientDlg::OnOK()
{
// TODO: Add extra validation here
UpdateData(FALSE); //初始化对话框中的数据
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2); //连接两个给定的无符号参数
err = WSAStartUp(wVersionReqested, &wsaData);
if(err != 0)
return;
if(LOBYTE(wsaData.wVersion) != 1) || HIBYTE(wsaData.wVersion) != 1) //LOBYTE()得到一个16bit数
最低(最右边)那个字节
{
WSACleanup();
return;
}
SOCKET TcpClient = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN SerAddr;
SerAddr.sin_family AF_INET;
SerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
/*
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;
};
*/
SerAddr.sin_port = htos(m_iPort);
connect(TcpClient, (SOCKADDR*)&SerAddr, sizeof(SerAddr)); //与服务器端建立连接
/*
int connect(
SOCKET s, //服务器端Socket的识别码
const struct sockaddr FAR* name, //Socket想要连接的对方地址
int namelen //地址长度
)
*/
long FileLength;
recv(TcpClient, (char *)&FileLength, sizeof(long), 0);
/*
int recv(
SOCKET s,
char FAR *buf, //存放接收到资料的暂存区
int len, //buf的长度
int flags //此函数的调用方式
)
*/
if(RecvFile("c:\\003.doc", TcpClient, FileLength))
MessageBox("传输结束!\n");
else
MessageBox("传输失败!\n");
CDialog::OnOK();
}
4>添加接收文件函数
BOOL CStreamSocketClientDlg::RecvFile(char *name, SOCKET conn, UINT filelen)
{
char *FileName = name;
SOCKET client = conn;
CFile file;
if(!file.Open(FileName, CFile::modeWrite))
{
printf("打开%s失败!\n", FileName);
return FALSE;
}
int NumBytes; //用来保存每次接收时数据块的大小
UINT Total = 0; //用来保存套接字已经接收的总的字节数
int BufSize = 1024; //接收缓冲区的大小
int Size = BufSize; //写文件的大小
LPBYTE pBuf = new BYTE[BufSize]; //接收缓冲区
DWORD dwTemp = 0;
file.SeekToBegin();
while(Total < filelen)
{
if((int)(filelen - Total) < Size)
Size = filelen - Total;
NumBytes = recv(client, (char *)pBuf, Size, 0);
if(NumBytes == SOCKET_ERROR)
{
printf("接收失败!\n");
return FALSE;
}
file.Write(pBuf, NumBytes);
/*
virtual void Write(
const void* lpBuf, //存储要写的东西的字符串或者字符数组
UINT nCount ); //要从这个字符串或者字符数组中写多少个字符到文件中
*/
Total += NumBytes;
file.Seek(Total, CFile::begin);
}
delete[] pBuf;
file.Close();
closesocket(client);
return TRUE;
}