大连开发区捷通电脑技术有限公司(116600)
套接字是支持TCP/IP协议的网络通信的基本操作单元。可以将套接字看做不同主机间的进程进行双向通信的端点。在网络编程中最常用的方案便是客户机/服务器模型。本文主要讨论C/S模型下用套接口实现UDP协议的网络通信。利用Socket而自定义了CSockAddr类(与地址有关的机能都封装在这里)和CBlockingSocket类(里面封装了Socket),通过实践证明,通过使用这两个自定义类,可使应用程序非常简练、易读。
一、Socket类型
根据传输数据类型的不同,套接字可分为面向连接的字节流套接字(streamsockets)和无连接的数据报套接字(datagramsockets)两种类型。
1. 字节流套接字
字节流不按记录定界,在TCP/IP协议簇中对应TCP协议,即传输控制协议。它是一个提供给用户进程可靠的全双工的面向连接的协议,大多数Internet应用程序如ftp、telnet使用TCP协议。
2. 数据报套接字
数据报对应记录型数据流,在TCP/IP协议簇中对应UDP协议,即用户数据报协议(UserDatagramProtocol)。由于不建立连接,数据报协议比连接协议快。但不能保证所有数据都准确有序地到达目的地,不保证顺序性、可靠性和无重复性。它是无连接的服务,以独立的信包进行传输,通信端点使用UDP对应的Internet地址。双方不需互连,按固定的最大长度进行传输,因而适用于单个报文传输。
二、数据报套接字的工作过程
不论何种套接字编程,均采用客户机/服务器方式,数据报套分别生成服务进程和客户进程,在通信前必须创建各自的套接字以建立连接,然后对相应的套接字进行"读""写"操作,实现信息的交换。如下图无连接协议的Socket编程模型。图1
三、编程示例
利用SOCKET而自定义了CSockAddr类(与地址有关的机能都封装在里面)和CBlocking Socket类(里面封装了SOCKET),然后用VC++生成两个对话框工程,一个是服务器程序SV,另一个是客户端程序CL。实践证明,通过使用这两个自定义类,可使应用程序非常简练、易读。
以下是CSockAddr类 和CBlockingSocket类这两个类的源程序。服务器程序SV和客户端程序CL 只列出了相关的部分。
/*** bsock.cpp :
** Block Socket class*/
// Socket地址类 只跟AF_INET对应
// 与地址有关的机能都在这里封装了。
typedef SOCKADDR *LPSOCKADDR;
class CSockAddr {
SOCKADDR_IN m_SockAddrIN;
public:
CSockAddr(LPCSTR address=NULL, int port=0, int
family=AF_INET); //构造CSockAddr
// 向LPSOCKADDR变换的运算
operator LPSOCKADDR (void) {return (LPSOCKADDR) &m_SockAddrIN;}
int GetSize(void) {return sizeof(m_SockAddrIN);} // IP情报的大小
BOOL IsIPAddress(LPCSTR address); // IP地址的检查nnn.nnn.nnn.nnn(n是[0-9])的形式检查
// IP 地址的设定
voidSetIPAddrees(LPCSTR ddress) {m_SockAddrIN. sin_addr.s_addr=::inet_addr(address);}
// 由主机名的IP地址的设定
BOOL SetIPAddressByHost(LPCSTR hostname)
{
struct hostent* host=gethostbyname(hostname);
if (host==NULL) {
TRACE("SERVER %s 没有找到 code=%d", hostname, WSAGetLastError());
return FALSE;
}
m_SockAddrIN.sin_addr.s_addr=*(int*)host->h_addr;
return TRUE;
}
// IP地址的文字的取得
CString GetIPString(void)
{
CString str;
str.Format("%d.%d.%d.%d",
m_SockAddrIN.sin_addr.S_un.S_un_b.s_b1,
m_SockAddrIN.sin_addr.S_un.S_un_b.s_b2,
m_SockAddrIN.sin_addr.S_un.S_un_b.s_b3,
m_SockAddrIN.sin_addr.S_un.S_un_b.s_b4
);
return str;
}
int GetPort(void) { return ntohs(m_SockAddrIN.sin_port);} // PORT 的取得
void Dump(void) // DEBUG 用
{
TRACE("famiry=%d\n",m_SockAddrIN.sin_family);
TRACE("port=%d\n", ntohs(m_SockAddrIN.sin_port));
TRACE("addr=%s\n", GetIPString());
}
};
// CBlockingSocket class
class CBlockingSocket {
protected:
public:
int m_BufferPointer; // 1文字接受BUFFER
int m_BufferLength;
BYTE m_RecvBuffer[1024];
int m_ReceiveTimeout; // 收数据TIMEOUT
// SOCKET HANDLE
SOCKET m_hSocket;
CBlockingSocket(void)//构造CBlockingSocket
{
m_ReceiveTimeout=INFINITE;
m_hSocket=INVALID_SOCKET;
m_BufferPointer=0;
m_BufferLength=0;
}
~CBlockingSocket(void) {} // DESTORY
// SCOKET的作成
BOOL Socket(int nSocketType=SOCK_STREAM, int
nProtocolType=IPPROTO_IP, int
nAddressFormat=PF_INET);
BOOL Bind(CSockAddr& addr)// SCOKET的绑定
{
if (bind(m_hSocket, addr, addr.GetSize())==SOCKET_ERROR) {
TRACE("bind() error\n");
return FALSE;
} else {
return TRUE;
}
}
void Attach(SOCKET sock) {m_hSocket=sock;} //
SCOKET的HANDLE设定
void Detach(void) {m_hSocket=INVALID_SOCKET;} // SCOKET的HANDLE切断
virtual void Close();// SCOKET的CLOSE
BOOL Listen(int nConnectionBacklog=5);//连接监听
virtual BOOL Accept(CBlockingSocket& rConnectedSocket CSockAddr* lpSockAddr=NULL);//连接认可
BOOL Connect(CSockAddr& addr)//请求连接
{
if (connect(m_hSocket, addr, addr.GetSize())==SOCKET_ERROR) {
return FALSE;
} else {
return TRUE;
}
}
// 接受数据TIMEOUT的设定
virtual void SetReceiveTimeout(int msec)
{m_ReceiveTimeout=msec;}
virtual int Receive(void* lpBuf, int nBufLen, int nFlags = 0); // 数据接收
virtual int Receive(void);
// 一个文字接受
virtual int Send(const void* lpBuf, int nBufLen, int nFlags = 0); // 送信
// 数据块送信
virtual int SendTo(const void* lpBuf, int nBufLen, CSockAddr& addr, int nFlags=0)
{
return sendto(m_hSocket, (const char*)lpBuf, nBufLen, nFlags, addr, addr.GetSize());
}
// 数据块接收
virtual int ReceiveFrom(void* lpBuf, int nBufLen, CSockAddr& addr, int nFlags=0)
{
SOCKADDR *sa=addr;
int len=addr.GetSize();
int ret=recvfrom(m_hSocket, (char*)lpBuf, nBufLen, nFlags, sa, &len);
return ret;
}
};
// end of file
/*** bsock.cpp :
** Block Socket class*/
#include "stdafx.h"
#include "winsock.h"
#include "bsock.h"
// SOCKADDR_IN构造体的作成CSockAddr::CSockAddr(LPCSTR address, int port, int family)
{
SOCKADDR_IN& sockAddr=m_SockAddrIN;
memset(&sockAddr,0,sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
//
if (address == NULL) {
sockAddr.sin_addr.s_addr=htonl(INADDR_ANY);
} else {
// IP 的数字或文字的判断
BOOL ipaddr=IsIPAddress(address);
if (!ipaddr) {
SetIPAddressByHost(address);
} else {
SetIPAddrees(address);
}
}
sockAddr.sin_port = htons((u_short)port);
}
// IP 地址的检查// nnn.nnn.nnn.nnn(n是[0-9])的形式检查
BOOL CSockAddr::IsIPAddress(LPCSTR address)
{
BOOL name=FALSE;
for (int i=0;address[i];i++) {
if ((!isdigit(address[i]))&&address[i]!=.') {
name=TRUE;
}
}
return (!name);
}
// SOCKET 作成
BOOL CBlockingSocket::Socket(int nSocketType, int nProtocolType, int nAddressFormat)
{
m_hSocket = socket(nAddressFormat, nSocketType, nProtocolType);
if (m_hSocket != INVALID_SOCKET)
return TRUE;
TRACE("Socket() Error\n");
return FALSE;
}
// SOCKET 关闭
void CBlockingSocket::Close()
{
if (m_hSocket != INVALID_SOCKET) {
closesocket(m_hSocket);
m_hSocket = INVALID_SOCKET;
}
}
// 连接认可
BOOL CBlockingSocket::Accept(CBlockingSocket& rConnectedSocket, CSockAddr* lpSockAddr)
{
SOCKET hTemp;
if (lpSockAddr) {
SOCKADDR* addr=*lpSockAddr;
int addrlen=lpSockAddr->GetSize();
hTemp=accept(m_hSocket, addr,&addrlen);
} else {
hTemp=accept(m_hSocket, NULL, NULL);
}
rConnectedSocket.Attach(INVALID_SOCKET);
if (hTemp == INVALID_SOCKET)
{
DWORD dwProblem = GetLastError();
TRACE("GetLastError()=%d\n", dwProblem);
SetLastError(dwProblem);
}
rConnectedSocket.Attach(hTemp);
return (hTemp != INVALID_SOCKET);
}
// 连接监听
BOOL CBlockingSocket::Listen(int nConnectionBacklog)
{
if (listen(m_hSocket, nConnectionBacklog)==SOCKET_ERROR) {
return FALSE;
} else {
return TRUE;
}
}
// 数据接收
int CBlockingSocket::Receive(void* lpBuf, int nBufLen, int nFlags)
{
if (m_ReceiveTimeout==INFINITE) {
return recv(m_hSocket, (LPSTR)lpBuf, nBufLen, nFlags);
} else {
fd_set rfds;
struct timeval tv;
int retval;
FD_ZERO(&rfds);
FD_SET(m_hSocket, &rfds);
//
tv.tv_sec = m_ReceiveTimeout/1000;
tv.tv_usec=(m_ReceiveTimeout % 1000)*1000;
retval=select(1,&rfds,NULL,NULL, &tv);
if (retval) {
return recv(m_hSocket, (LPSTR)lpBuf, nBufLen, nFlags);
} else {
return 0;// Timeout
}
}
}
// 一个文字接受
int CBlockingSocket::Receive()
{
int ret=0;
if (m_BufferPointer>=m_BufferLength) {
m_BufferPointer=0;
m_BufferLength=Receive((char*)m_RecvBuffer, sizeof(m_RecvBuffer));
if (m_BufferLength==0) return -1;
}
return m_RecvBuffer[m_BufferPointer++];
}
// 送信
int CBlockingSocket::Send(const void* lpBuf, int nBufLen, int nFlags)
{
return send(m_hSocket, (LPSTR)lpBuf, nBufLen, nFlags);
}
// End if file.
服务器程序SV
// SVDlg.h 中定义
HANDLE m_hThread;
static DWORD WINAPI ThreadProc(CSVDlg* dlg);
// SVDlg.CPP 加入
BOOL CSVDlg::OnInitDialog()
{
....
DWORD threadID;
m_hThread=CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)
CSVDlg::ThreadProc,
(LPVOID)this, 0, &threadID);
return TRUE; // return TRUE unless you set the focus to a control
}
DWORD WINAPI CSVDlg::ThreadProc(CSVDlg* dlg)
{
CBlockingSocket socket;
socket.Socket(SOCK_DGRAM, IPPROTO_UDP);
CSockAddr addr(NULL, 10000);
addr.Dump();
socket.Bind(addr);
while (1) {
CSockAddr from;
char buf[128];
int len=socket.ReceiveFrom(buf, sizeof(buf), from);
if (len>0) {
buf[len]=0;
CString str;
str.Format("%s %s %d\n", buf, from.GetIPString(), from.GetPort());
::MessageBox(dlg->m_hWnd,str,"SV",MB_OK);
socket.SendTo(buf, len, from);
} else
break;
}
socket.Close();
return 0;
}
客户端程序CL 中加入:
void CCLDlg::OnSendButton()
{
CString ip;
m_IPEdit.GetWindowText(ip);
CBlockingSocket socket;
socket.Socket(SOCK_DGRAM, IPPROTO_UDP);
CSockAddr addr(NULL, 10001);
socket.Bind(addr);
CSockAddr sendaddr(ip, 10000);
socket.SendTo("test", 4, sendaddr);
char buf[128];
int len=socket.ReceiveFrom(buf, sizeof(buf), addr);
buf[len]=0;
CString str;
str.Format("%s %s %d\n", buf, addr.GetIPString(), addr.GetPort());
MessageBox(str, "CL", MB_OK);
socket.Close();
}
程序在VC++ 6.0 下编译通过,在使用 TCP/IP 协议的 Windows 95/98对等局域网和使用 TCP/IP 协议的 Windows NT 局域网上运行良好。