一.套接字的类型
1.流式套接字(SOCK_STREAM)
基于TCP协议实现的。
提供面向连接、可靠的数据传输服务,数据无差错、无重复的发送,且按发送顺序接受。
2.数据报式套接字(SOCK_DGRAM)
基于UDP协议。
提供无连接服务。数据包以独立包形式发送,不提供无错保证,数据可能重复或丢失,并且接受顺序混乱。
3.原始套接字(SOCK_RAM)
二.基于TCP(面向连接)的socket编程
1.服务器端的程序流程
- 创建套接字(socket):该套接字用来监听。
- 将套接字绑定到一个本地地址和端口上(bind)。
- 将套接字设为监听模式,准备接受客户请求(listen)。
- 等待客户请求的到来,当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)。
- 用返回的套接字和客户端进行通信(send/recv)。
- 返回,等待另一客户请求。
- 关闭套接字。
总结如下:create socket -> bind -> listen -> accept -> send/recv -> close
2.客户端的程序流程
- 创建套接字(socket)。
- 向服务器发出连接请求(connect)。
- 和服务器进行通信(send/recv)。
- 关闭套接字。
总结如下:create socket -> connect -> send/recv -> close
3.一个基于TCP的socket通信的例子
(1).服务器端(server)
使用2个socket:一个sockSrv,用来监听客户请求,等待客户请求的到来;另一个是sockConn,用来和客户端通信的(发送/接受)。
#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
return;
}
/* Confirm that the WinSock DLL supports 2.2.*/
/* Note that if the DLL supports versions greater */
/* than 2.2 in addition to 2.2, it will still return */
/* 2.2 in wVersion since that is the version we */
/* requested. */
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
WSACleanup( );
return;
}
/* The WinSock DLL is acceptable. Proceed. */
//create a socket to listen
SOCKET sockSrv = socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv;
//INADDR_ANY表示IP为0.0.0.0,表示所在机器的所有地址,因为对于多网卡的机器
//会有多个IP,那么用0.0.0.0进行socket的bind,则所有网卡都可监听到
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(7777);
//bind the socket
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
//set the socket to be listening and prepare to receive the client request
listen(sockSrv , 5 );
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while (1)
{
//wait client request
SOCKET sockConn = accept(sockSrv,(SOCKADDR*)&addrClient,&len);
char sendBuf[100]= "welcome 11";
sprintf(sendBuf,"Welcome %s to here!",inet_ntoa(addrClient.sin_addr));
//send data
send(sockConn,sendBuf,strlen(sendBuf)+1,0);
char recvBuf[100];
//receive data
recv(sockConn,recvBuf,100,0);
printf("%s \n",recvBuf);
closesocket(sockConn);
}
getchar();
return;
}
(2).客户端(client)
只用一个socket:sockClient,进行通信。
#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(1,1);
err = WSAStartup(wVersionRequested,&wsaData);
if (err != 0)
{
return;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return;
}
//create a socket
SOCKET sockClient = socket(AF_INET,SOCK_STREAM,0);
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(7777);
//send a connect request
connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
//receive data
char recvBuf[100];
recv(sockClient,recvBuf,100,0);
printf("%s \n",recvBuf);
//send data
send(sockClient,"welcome zhaolianxiang",strlen("welcome zhaolianxiang")+1,0);
//close socket
closesocket(sockClient);
WSACleanup();
getchar();
}
三.基于UDP(面向无连接)的socket编程
基于UDP的socket编程中,其服务器端和客户端的概念不是很强化。可以把先启动的一端称为接收端(服务器端),发送数据的一端成为发送端(客户端)。
1.服务器端(接收端)的程序流程
- 创建套接字(socket)。
- 将套接字绑定到一个本地地址和端口上(bind)。
- 等待接受数据(recvfrom)。
- 关闭套接字(close)。
总结:create socket -> bind ->recvfrom ->close。
2.客户端(发送端)的程序流程
- 创建套接字(socket)。
- 向服务器发送数据(sendto)。
- 关闭套接字(close)。
总结:create socket ->sendto ->close。
3.一个基于UDP的socket通信的例子
(1).接收端(服务器)
只用一个socket:sockSrv进行通信。
#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);//创建套接字
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)); //绑定套接字
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
char recvBuf[100];
recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);//等待并接受数据
printf("%s\n",recvBuf);
closesocket(sockSrv);
WSACleanup();
}
(2).发送端(客户端)
只用一个socket:sockClient进行通信。
#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);
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);
sendto(sockClient,"Hello",strlen("Hello")+1,0,
(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//发送数据
closesocket(sockClient); //关闭套接字
WSACleanup();
}
四.TCP socket和UDP socket的比较
TCP socket模式:C/S模式,即客户端/服务器端。适用于一个server,多个client,即一对多。使用send/recv函数发送和接受数据。
UDP socket模式:发送端/接收端模式,即先启动的一端为接收端。适用于一对一的情况,比如聊天。即client <----> client。使用sendto/recvfrom函数发送和接受数据。
五.一个基于UDP的简单聊天程序
(1).服务器端
#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
void main()
{
WORD wVersionRequested;
WSAData wsaData;
int err;
wVersionRequested = MAKEWORD(2,2);
err = WSAStartup(wVersionRequested,&wsaData);
if (err != 0)
{
return;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return;
}
//create a socket
SOCKET sockSrv = socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(5555);
//bind the socket
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
char recvBuf[1000];
char sendBuf[1000];
char tempBuf[1000];
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while (1)
{
//receive data
recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
if ('q' == recvBuf[0])
{
sendto(sockSrv,"q",strlen("q")+1,0,(SOCKADDR*)&addrClient,len);
printf("chat end!\n");
break;
}
sprintf(tempBuf,"%s say: %s",inet_ntoa(addrClient.sin_addr),recvBuf);
printf("%s \n",tempBuf);
//send data
printf("pls input data:\n");
gets(sendBuf);
sendto(sockSrv,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrClient,len);
}
closesocket(sockSrv);
WSACleanup();
getchar();
};
分析:服务器端(接收端)是先接受数据,再发送数据。只有一个socket对象用来通信:sockSrv,。
(2).客户端
#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
void main()
{
WORD wVersionRequested;
WSAData wsaData;
int err;
wVersionRequested = MAKEWORD(2,2);
err = WSAStartup(wVersionRequested,&wsaData);
if (err != 0)
{
return;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return;
}
SOCKET sockClient = socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(5555);
int len = sizeof(SOCKADDR);
char recvBuf[100];
char sendBuf[100];
char tempBuf[100];
while (1)
{
printf("pls input data:\n");
gets(sendBuf);
sendto(sockClient,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)(&addrSrv),len);
recvfrom(sockClient,recvBuf,100,0,(SOCKADDR*)&addrSrv,&len);
if ('q' == recvBuf[0])
{
sendto(sockClient,"q",strlen("q")+1,0,(SOCKADDR*)&addrSrv,len);
printf("chat end!\n");
break;
}
sprintf(tempBuf,"%s says: %s",inet_ntoa(addrSrv.sin_addr),recvBuf);
printf("%s \n",tempBuf);
}
closesocket(sockClient);
WSACleanup();
getchar();
};
分析:客户端(发送端)是先发送数据,后接受数据。只用一个socket:sockClient进行通信。