1、Socket编程的主要步骤与重要函数
Socket编程在windows上首先需要<WinSock2.h>这个头文件和“ws2_32.lib”这个lib库文件。
Soket编程一般分为UDP通信与TCP通信,那么简单的UDP通信与TCP通信前面的过程基本都是一样的,只有在收发数据的时候会有不同。
UDP通信的一般步骤:
(1)、初始化
WSADATA wd;
WSAStartup(0x0202,&wd); //初始化加载ws2_32.lib
WSAStartup是Socket编程的初始化工作
(2)、建立套接字
SOCKET sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock == INVALID_SOCKET)
{
cout<<"socket创建失败! 错误码:"<<WSAGetLastError()<<endl;
}
SOCKET socket(int af, int type,int protocol);
socke函数是建立套接字,返回一个描述符指向成功的socket。
af : 一般指定为 AF_INET,表示使用UDP,TCP网络
type: 一般分为两类,SOCK_STREAM 表示TCP通信,SOCK_DGRAM表示UDP通信
protocol: 一般设定为0。
(3)、绑定端口
int bind(SOCKET s,const sockaddr* name,int namelen)
bind函数将套接字绑定在指定的端口上,绑定成功返回0,绑定失败返回-1
s: 套接字
name: sockaddr结构体,sockaddr_in与sockaddr可以互相转化,一般常用sockaddr_in,最后再强制转化为sockaddr
sockaddr_in结构体如下
struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
sin_family: 指定为AF_INET,表示UDP,TCP家族
sin_port: 指定端口
sin_addr: 指定IP
sin_zreo: 保证sockaddr_in与sockaddr有相同的大小,便于相互转化
实例:
const int PORT = 8080;
sockaddr_in sa = {AF_INET,htons(PORT)};
int n = bind(sock,(sockaddr*)&sa,sizeof(sa));
if(SOCKET_ERROR == n)
{
cout<<"bind绑定端口失败! 错误码:"<<WSAGetLastError()<<endl;
}
套接字绑定了8080端口,其中端口需要用htons反转下,因为网络上的字节序与计算机上的相反,如果是服务器端绑定端口可以不设置IP,这样服务器可以接受本机上所有网段的消息。
(4)、监听
监听只有是TCP服务器端才会有的。
int listen(
SOCKET s,
int backlog
)
s: 套接字
backlog: The maximum length of the queue of pending connections.
(5)、等待连接
只有TCP服务器端才会有,服务器端等待客户端的socket连接
SOCKET
accept(
SOCKET s,
struct sockaddr* addr,
int* addrlen
);
接受成功,则返回一个指向客户段的套接字。
s: 服务器端的套接字
addr: 用于保存客户端的信息(包括:IP,端口等)
addrlen: addr结构体的大小
(6)、连接
只有客户端才会有,向服务器端发送连接请求
int
connect(
SOCKET s,
const struct sockaddr* name,
int namelen
s: 客户端的套接字
name: 服务器端地址信息(需要制定IP,端口)
namelen: 结构体name的大小
连接成功,则返回非0,否则返回0
(7)、发送数据
发送数据分为UDP发送与TCP发送,UDP发送使用sendto函数,TCP发送使用send函数
int
sendto(
SOCKET s,
const char FAR * buf,
int len,
int flags,
const struct sockaddr FAR * to,
int tolen
);
s: 发送端的套接字
buf: 发送数据的缓存
len: 发送数据的大小
flags: 设为0
to: 接收端的地址信息(需要指定IP,端口)
tolen: 接受端结构体的大小
函数返回实际发送数据的大小
int
send(
SOCKET s,
const char FAR * buf,
int len,
int flags
);
参数: 与senfto类同
(8)、接受数据
接受数据有两个函数,recv 与 recvfrom
int
recv(
SOCKET s,
char FAR * buf,
int len,
int flags
);
s: 接收端(服务器端)的套接字
buf: 接受缓存
len: 缓存大小
flags: 一般设为0
函数返回实际接受的大小,-1表示接受失败
int
recvfrom(
SOCKET s,
char FAR * buf,
int len,
int flags,
struct sockaddr FAR * from,
int FAR * fromlen
);
from: 发送端的地址信息
fromlen: from结构体的大小
recvfrom 与 recv 最大的区别就是,recvfrom可以获取发送端的地址信息(包括:IP与端口)
(9)、getpeername 与 getsockname
getpeername可以获取对方IP,端口信息(只限TCP)
getsockname可以获取本机IP,端口信息
int
getpeername(
SOCKET s,
struct sockaddr FAR * name,
int FAR * namelen
)
s: 发送方的套接字
name: 保存发送方的地址信息
namelen: name结构体的大小
int
getsockname(
SOCKET s,
struct sockaddr FAR * name,
int FAR * namelen
);
s: 本机的套接字
其余:类同
2、示例
UDP通信--发送端
// test_UDP_send.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <WinSock2.h>
#include <iostream>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wd;
WSAStartup(0x0202,&wd);
SOCKET sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock == INVALID_SOCKET)
{
cout<<"sock 创建失败!"<<endl;
return -1;
}
sockaddr_in sa = {AF_INET,htons(7070)};
int n = bind(sock,(sockaddr*)&sa,sizeof(sa));
if(n == SOCKET_ERROR)
{
cout<<"bind 失败!"<<endl;
return -1;
}
sockaddr_in accept = {AF_INET,htons(8080)}; //指定接受端的端口与IP
accept.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
char s[128];
while(1)
{
cout<<"请输入发送的数据: ";
gets(s);
sendto(sock,s,strlen(s),0,(sockaddr*)&accept,sizeof(accept));
}
return 0;
}
UDP通信--接收端
// test_UDP_recv.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "stdafx.h"
#include <WinSock2.h>
#include <iostream>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wd;
WSAStartup(0x0202,&wd);
SOCKET sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock == INVALID_SOCKET)
{
cout<<"sock 创建失败!"<<endl;
return -1;
}
sockaddr_in sa = {AF_INET,htons(8080)};
int n = bind(sock,(sockaddr*)&sa,sizeof(sa)); //绑定了8080端口
if(n == SOCKET_ERROR)
{
cout<<"bind 失败!"<<endl;
return -1;
}
char s[128]; //接受数据的缓存
n = 0;
while((n = recv(sock,s,sizeof(s)-1,0))>0)
{
s[n] = 0;
cout<<s<<endl;
}
return 0;
}
TCP通信--服务器端
// test_TCP_server.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <WinSock2.h>
#include <iostream>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wd;
WSAStartup(0x0202,&wd);
SOCKET sock = socket(AF_INET,SOCK_STREAM,0);
if(sock == INVALID_SOCKET)
{
cout<<"sock 失败!"<<endl;
return -1;
}
sockaddr_in sa = {AF_INET,htons(8080)};
int n = bind(sock,(sockaddr*)&sa,sizeof(sa));
if(n == SOCKET_ERROR)
{
cout<<"bind 失败!"<<endl;
return -1;
}
listen(sock,5);//监听
SOCKET acceptSock;
sockaddr_in client = {0};
int nLen = sizeof(client);
if((acceptSock = accept(sock,(sockaddr*)&client,&nLen)) == INVALID_SOCKET)//等待连接,会阻塞在这里
{
cout<<"accept 失败!"<<endl;
return -1;
}
cout<<"有客户端连接:"<<inet_ntoa(client.sin_addr)<<"-"<<htons(client.sin_port)<<endl;
char s[128];
while((n = recv(acceptSock,s,sizeof(s)-1,0)) > 0 )
{
s[n] = 0;
cout<<s<<endl;
}
return 0;
}
TCP通信--客户端
// test_TCP_client.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <WinSock2.h>
#include <iostream>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wd;
WSAStartup(0x0202,&wd);
SOCKET sock = socket(AF_INET,SOCK_STREAM,0);
if(sock == INVALID_SOCKET)
{
cout<<"sock 失败!"<<endl;
return -1;
}
sockaddr_in sa = {AF_INET}; //客户端不指定端口,系统会分配一个随机端口
int n = bind(sock,(sockaddr*)&sa,sizeof(sa));
if(n == SOCKET_ERROR)
{
cout<<"bind 失败!"<<endl;
return -1;
}
sockaddr_in server = {AF_INET,htons(8080)}; //指定服务器端的端口与IP
server.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
n = connect(sock,(sockaddr*)&server,sizeof(server));
if(n == SOCKET_ERROR)
{
cout<<"连接服务器失败!"<<endl;
return -1;
}
else
cout<<"连接服务器成功!"<<endl;
char s[128];
while(1)
{
cout<<"请输入发送的数据:";
gets(s);
send(sock,s,strlen(s),0);
}
return 0;
}