您的位置:首页 > 理论基础 > 计算机网络

【网络编程学习笔记01】Socket套接字编程(TCP/IP)

2013-02-21 23:10 1036 查看
前置知识
1.有一定C++编程基础

2.套接字的主要类型

有两种:一种是流式套接字(SOCK_STREAM),一种是数据报套接字(SOCK_DGRAM),

分别用于TCP/IP协议和UDP协议进行通讯的应用程序中。

3.TCP/IP寻址方式

  用户使用IP地址和端口号进行确定通信双方。

-----------------------------------------------------------------------------

一、Winsock编程流程
1.配置编程环境

由于所有的Winsock函数均是从动态链接库WS2_32.DLL中导出的,而VC默认是没有与该库进行连接,所以我们需要进行编程环境设置。添加方法是选择“工程--->设置--->连接”



2.初始化套接字库

调用WSAStartup()对该库进行初始化,查询MSDN后得知原型如下

int WSAStartup(WORD wVersionRequested , LPWSADATA lpWSAData);

该函数调用成功,将返回0。

参数wVersionRequested表示当前套接字库的版本号。

例如当前套接字版本号为2.0,则将该参数设置为2.0,代码如下:

<pre class="cpp" name="codeWORD wVersionRequested=MAKEWORD(2,0);
参数lpWSAData指向结构体WSADATA的指针变量,表示获取到的套接字库详细信息。

typedef struct WSAData {
WORD wVersion;			//库文件建议使用的版本号
WORD wHighVersion;			//库文件支持的最高版本
char szDescription[WSADESCRIPTION_LEN+1]; //描述库文件的字符串
char szSystemStatus[WSASYS_STATUS_LEN+1]; //系统状态字符串
unsigned short iMaxSockets;		//用于设置客户端的最大连接数量
unsigned short iMaxUdpDg;		//新版本已经无用
char FAR * lpVendorInfo;		//新版本已经无用
} WSADATA, *LPWSADATA;

例1:有了上述知识,就可以开始初始化套接字库了,代码如下:

WSADATA data;								//定义WSAData指针对象
WORD w=MAKEWORD(2,0);						//定义当前套接字库版本号
::WSAStartup(w,&data);						//初始化套接字库,为默认参数

3.创建套接字句柄

创建套接字句柄的函数是socket(),查询MSDN后得知该函数原型如下

SOCKET socket(
int af,       //指定套接字所使用的地址格式,TCP/IP设置为AF_INET
int type,     //套接字类型,见前置知识第2条。
int protocol  //一般设置为0,想深究,请百度。
);
该函数执行成功,将返回新创建的套接字句柄。否则,将返回INVALID_SOCKET。
例2:创建流式套接字(TCP/IP)句柄的代码如下:

SOCKET s;								//定义套接字句柄对象
s=::socket(AF_INET,SOCK_STREAM,0); 		//创建并返回套接字句柄
4.构造套接字地址结构
sockaddr_in是TCP/IP地址家族中统一的套接字地址结构,查询MSDN得知原型如下

struct sockaddr_in{
short 		   sin_family;	   //指定地址家族,TCP/IP设置为AF_INET
unsigned short   sin_port;	   //端口
struct in_addr   sin_addr;	   // IP地址
char			   sin_zero[8];	   //一般设置为0
};

该结构成员变量in_addr结构定义如下:

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;
};

通常,我们在编写网络程序时,使用一个u_long类型的字符串进行描述IP地址。

例3:127.0.0.1是回送地址,指向本机,一般可以用来测试,代码如下:

sockaddr_in addr;									//定义结构对象
addr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");

编程知识普及:字节顺序

字节顺序分为:Big-Endian和Little-Endian。以unsigned int val=0x12345678为例

Intel CPU(Little-Endian)存放的字节顺序为0x78、0x56、0x34、0x12。

而网络的字节顺序为(Big-Endian) 0x12、0x34、0x56、0x78。正好相反,但更符合人

们的阅读情况。所以编程人员将数据通过网络发送时,需要将存储在本地计算机上的数

据转换成以网络字节顺序排列的数据。从数据的角度来说,网络字节顺序将最重要的数

据先进行存储,而Intel CPU字节顺序则将不重要的字节首先储存。

字节顺序转换函数

u_short htons(u_short hostshort);
//将一个u_short类型的端口号从主机字节顺序转换到网络字节顺序。
u_short ntohs(u_short netshort);
//将一个u_short类型的端口号从网络字节顺序转换到主机字节顺序。
u_long htonl(u_long hostlong);
//将一个u_long类型的IP地址从主机字节顺序转换到网络字节顺序。
u_long ntohl(u_long netlong);
//将一个u_long类型的IP地址从网络字节顺序转换到主机字节顺序。
unsigned long inet_addr(const char FAR *cp);
//将一个字符串IP转换到以网络字节顺序排列的IP地址。
char FAR * inet_ntoa(struct in_addr in);
//将一个以网络字节顺序排列的IP地址转化为一个字符串IP。

例4:在构造套接字地址结构中使用字节顺序转换函数

sockaddr_in addr;				//定义套接字地址结构对象
addr.sin_family=AF_INET;			//指定地址家族为TCP/IP
addr.sin_port=htons(80);			//指定端口号
addr.sin_addr.S_un.S_addr="127.0.0.1";	//将字符串IP转换为网络字节顺序排列的IP
char addRes[]=inet_ntoa(addr.sin_addr.S_un.S_addr);	//将网络字节顺序排列的IP转换为字符串IP

5.绑定地址信息(服务端)

对于服务器,套接字创建成功后,还应该使用bind()函数将套接字与地址结构信息相

关联,bind()函数原型如下:

int bind(
SOCKET s,                          //套接字句柄
const struct sockaddr FAR *name,   //地址结构信息
int namelen                        //地址结构大小
);

该函数调用成功,则返回0。

例5:将套接字句柄绑定到本地地址,代码如下:

sockaddr_in addr;						//定义套接字地址结构对象
addr.sin_family=AF_INET;				//指定地址家族为TCP/IP
addr.sin_port=htons(80);				//指定端口号
addr.sin_addr.S_un.S_addr=INADDR_ANY;	//服务器能接受任何计算机发来的请求
::bind(s,(sockaddr*)&addr,sizeo
cfbd
f(addr));//绑定套接字到指定地址结构

6.监听端口信息(服务端)

服务器程序可以调用listen()函数实现监听端口的功能,函数原型如下:

int listen(
SOCKET s,    							//实现监听功能的套接字句柄
int backlog  							//指定监听的最大连接数量
);

如果多个客户端同时向服务器发出连接请求,并且超过了最大连接数,客户端将返回错误代码。

例6:在套接字上进行监听,并且将最大监听数量定义为5:

::listen(s,5);

7.连接(客户端)

客户端连接服务端需要用到connect()函数,函数原型如下:

int connect(
SOCKET s,                          //实现连接功能的套接字句柄
const struct sockaddr FAR *name,   //将要连接的服务器地址信息的结构指针
int namelen                        //服务器地址信息结构体长度
);

例7:连接地址为"127.0.0.1",端口为80的服务器。代码如下:

sockaddr_in addr;					 //定义套接字地址结构对象
addr.sin_family=AF_INET;			 //指定服务器地址家族为TCP/IP
addr.sin_port=htons(80);			 //指定要连接的端口号
addr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//指定服务器IP地址
::connect(s,(sockaddr*)&addr,sizeof(addr)); 	 //连接服务器

8.接受连接请求(服务端)

当服务端收到客户端的连接请求时,则可以调用accept()函数接受该请求,函数原型:

SOCKET accept(
SOCKET s,							 //套接字句柄
struct sockaddr FAR *addr,		 //保存客户端的地址信息于addr中
int FAR *addrlen					 //保存客户端的地址信息长度于addrlen中
);

例8:服务端套接字句柄为s,使用accept()函数接收客户端信息,代码如下:

SOCKET s1;
sockaddr_in addrConnect;
int n=sizeof(addrConnect);
s1=::accept(s,(sockaddr*)&addrConnect,&n);

9.数据收发

当服务器和客户端成功连接时,我们可以使用send()和recv()函数实现数据的发送和接收功能,函数原型如下:

int send(SOCKET s, const char FAR *buf, int len, int flags);
int recv(SOCKET s, char FAR *buf, int len, int flags);

两个函数的各个参数以及表示的意义都是相同的,参数*buf是指向数据缓冲区的指针变量,参数len是数据的长度,参数flags一般设置为0。

注意:对于服务端,参数s应该为接收客户端连接请求后,返回的新套接字句柄。

      对于客户端,参数s应该为客户端创建的套接字句柄。

例9:使用send()、recv()函数来发送、接收数据,代码如下:

char szText[]="Hello C++ Socket World";		//用于储存发送的数据
::send(s1,szText,sizeof(szText),0);			//发送数据给客户端s1
char szText2[100]={0};						//用于保存接收到的数据
::recv(s,szText2,sizeof(szText2),0);		//接收服务端发来的数据

10.关闭套接字

当套接字使用完毕或者程序退出时,应该调用函数closesocket()关闭套接字句柄,函数原型如下:

int closesocket(
SOCKET s  			//需要关闭的套接字句柄
);

例10:关闭之前创建的套接字句柄s,代码如下:

::closesocket(s);

11.释放套接字库

例11:当程序退出时,还应该记得调用WSACleanup()函数释放该套接字库,代码如下:

::WSACleanup();

二、Winsock编写过程
服务端编写过程:                        客户端编写过程:

1.初始化套接字库:WSAStartup()          1.初始化套接字库:WSAStartup()

2.创建套接字句柄:socket()              2.创建套接字句柄:socket()

3.构造套接字地址结构:sockaddr_in       3.构造套接字地址结构:sockaddr_in

4.绑定地址信息:bind()                  4.连接服务端:connect

5.监听端口信息:listen()                5.数据收发:send()、recv()

6.接受连接请求:accept()                6.关闭套接字:closesocket()

7.数据收发:send()、recv()              7.释放套接字库:WSACleanup()

8.关闭套接字:closesocket()

9.释放套接字库:WSACleanup()
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息