利用流式套接字傳輸資料檔案
阿新 • • 發佈:2019-02-03
伺服器端:
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;
}
介面: