套接字的类型:
1. 流式套接字(SOCK_STREAM)
提供面向连接,可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接收
2. 数据报式套接字(SOCK_DGRAM)
提供无连接服务,数据包以独立包形式发送,不提供无错保证,数据可能丢失或重复,并且接收顺序混乱
3. 原始套接字(sock_raw)
基于TCP(面向连接)的socket编程
服务器端程序:
1. 创建套接字(socket)
2. 将套接字绑定到一个本地地址和端口上(bind)
3. 将套接字设为监听模式,准备接收客户请求(listen)
4. 等待客户请求到来;当请求到来后, 接受连接请求,返回一个新的对应于此次连接的套接字(accept)
5. 用返回的套接字和客户端进行通信(send/recv)
6. 返回, 等待另一客户请求
7. 关闭套接字
客户端程序:
1.创建套接字(socket)
2。向服务器发出连接请求(connect)
3。和服务器端进行通信(send/recv)
4。关闭套接字.
A2 基于TCP的Socket连接步骤
控制台TCP服务器端接收发送消息
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );//版本号1.1
//1.加载套接字库
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
{
return;
}
//判断是否我们请求的winsocket版本,如果不是清除SOCKET后退出
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 )
{
WSACleanup( );
return;
}
//2.创建套接字
SOCKET sockSrv = socket(AF_INET,
SOCK_STREAM,//创建流式套接字
0); //零表示自动选择协议
//3.将套接字绑定到本地地址与端口上
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
//用htonl()方法将INADDR_ANY转换为网络字节序
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
//htons把u_short类型从主机字节序转换为网络字节序
bind(sockSrv,(SOCKADDR *) &addrSrv,sizeof(SOCKADDR));
//SOCKADDR大小写是一样的
//4.设置监听,5是最大监听数
listen(sockSrv,5);
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while(1)
{
SOCKET sockConn = accept(sockSrv,(SOCKADDR *) &addrClient,&len);
char sendBuf[100];
sprintf(sendBuf,"Welcome %syuliqi",inet_ntoa(addrClient.sin_addr));
send(sockConn,sendBuf,strlen(sendBuf)+1,0);
char recvBuf[100];
recv(sockConn,recvBuf,100,0);
printf("%s/n",recvBuf);
closesocket(sockConn);
}
}
控制台TCP客户端接收发送消息
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );//版本号1.1
//1.加载套接字库
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
{
return;
}
//判断是否我们请求的winsocket版本,如果不是清除SOCKET后退出
if ( LOBYTE( wsaData.wVersion ) != 1 ||HIBYTE( wsaData.wVersion ) != 1 )
{
WSACleanup( );
return;
}
//2.创建套接字
SOCKET sockClient = socket (AF_INET, SOCK_STREAM, 0);
//3.连接服务器端,无须绑定
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//设定服务器端IP地址,
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000); //端口号要与服务器端保持一致
//4.接收服务器端发送的数据,并且向服务器端发送数据
connect(sockClient,(SOCKADDR *)&addrSrv,sizeof(SOCKADDR));
char recvBuf[100];
recv(sockClient,recvBuf,100,0);
printf("%s /n", recvBuf);
//发送信息
send(sockClient,"I'm teshorse",strlen("I'm teshorse")+1,0);
//5.关闭套接字,释放资源,并且终止对套接字库的使用
closesocket(sockClient);
WSACleanup();
}
阻塞模式下的套接字会因为等待消息而是进程处于挂起状态应当,使用多线程可以解决这个问题
多线程TCP客户端接收发送消息
1.在APP的InitInstance中初始化套接字,需要加入Afxsock.h头文件
BOOL CChatApp::InitInstance()
{
if(!AfxSocketInit())//加载套接字库,进行版本协商
{
AfxMessageBox("加载套接字失败!");
return FALSE;
}
}
2.定义结构体, 通过LPVOID类型的指针传递到进程中
struct RECVPARAM
{
SOCKET sock;//连接的socket
HWND hwnd;//发送消息的句柄
};
3.定义成员变量套接字SOCKET m_socket;
4.定义初始化套接字的函数InitSocket()
BOOL CThreadchatDlg::InitSocket()
{
//初始化套接字:
m_socket = socket(AF_INET,SOCK_DGRAM,0);
if(INVALID_SOCKET == m_socket)
{
AfxMessageBox("套接字创建失败");
return FALSE;
}
SOCKADDR_IN addrSock;
addrSock.sin_family=AF_INET;
addrSock.sin_port=htons(6000);
addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
//INADDR_ANY表示接收任意IP地址
int retval;
//服务器端绑定
retval=bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR));
if(SOCKET_ERROR==retval)
{
closesocket(m_socket);
AfxMessageBox("绑定失败");
return FALSE;
}
return TRUE;
}
在OnInitDialog()函数中添加如下代码:
InitSocket(); //初始化套接字
RECVPARAM *pRecvParam=new RECVPARAM;//用来传递参数的结构体
pRecvParam->hwnd=m_hWnd;
pRecvParam->sock=m_socket;
HANDLE hThread=CreateThread(NULL,0,RecvProc/*线程函数*/,
(LPVOID)pRecvParam,0,NULL);//创建线程
CloseHandle(hThread);
//关闭线程句柄,这样当线程结束时,线程内核对象被释放,否则只有当进程结束,才释放线程的内核对象
//线程回调函数要定义成static成员函数
DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
{
SOCKET sock=((RECVPARAM*)lpParameter)->sock;
HWND hwnd=((RECVPARAM*)lpParameter)->hwnd;
SOCKADDR_IN addrFrom;
int len=sizeof(SOCKADDR);
char recvBuf[200];
char tempBuf[400];
int retval;
while(TRUE)
{
retval=recvfrom(sock,recvBuf,200,NULL,
(SOCKADDR*)&addrFrom,&len);
if(SOCKET_ERROR==retval)
break;
sprintf(tempBuf,"%s said:%s",
inet_ntoa(addrFrom.sin_addr),recvBuf);
::PostMessage(hwnd,WM_RECVDATA,0,(LPARAM)tempBuf);
//通过发送消息方式把数据传给对话框,
//传递一个自定义消息WM_RECVDATA
//将数据通过lParam进行传递。
}
return 0;
}
定义消息WM_RECVDATA实现向接收框传递数据
void CChatDlg::OnRecvData(WPARAM wParam,LPARAM lParam)
{
CString str=(char*)lParam;
CString strTemp;
GetDlgItemText(IDC_EDIT_RECV,strTemp);
// str+="/r/n";//换行需要变更编辑框的属性
str=strTemp+"/r/n"+str;
SetDlgItemText(IDC_EDIT_RECV,str);
}
接收消息的代码完成,下面完成的是发送消息的代码
void CThreadchatDlg::Onsendmsg()
{
//1.首先要得到IP地址
DWORD dwIP;
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
//2.定义地址结构体变量
SOCKADDR_IN addrTo;
addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
addrTo.sin_family=AF_INET;
addrTo.sin_port=htons(6000);
//3.得到发送编辑框口要发送的数据
CString strSend;
GetDlgItemText(IDC_EDIT_SEND,strSend);
//4.发送数据
sendto(m_socket,strSend,strSend.GetLength()+1,0,
(SOCKADDR*)&addrTo,sizeof(SOCKADDR));
SetDlgItemText(IDC_EDIT_SEND,"");
//把发送框设为空白
}