网络和黑客编程基本知识( 三)

                              第四节 关于服务器和客户端编程

在网络编程中,最常用和最基础的就是WINSOCK. 现在我们讨论WINDOWS下的SOCKET编程.

大凡在WIN32平台上的WINSOCK编程都要经过下列步骤:
定义变量->获得WINDOCK版本->加载WINSOCK库->初始化->创建套接字->设置套接字选项->关闭套接字->卸载WINSOCK库->释放资源

下面介绍WINSOCK C/S的建立过程:

服务器                        客户端
________________________________________________
1  初始化WSA                     1  初始化WSA
____________________________________________________
2  建立一个SOCKET                2  建立一个SOCKET
_____________________________________________________
3  绑定SOCKET                    3  连接到服务器
_____________________________________________________
4  在指定的端口监听              4  发送和接受数据
_____________________________________________________
5  接受一个连接                  5   断开连接
______________________________________________________-
6  发送和接受数据
___________________________________________________
7  断开连接
__________________________________________________

大家注意,在VC中进行WINSOCK编程时,需要引入如下两个库文件:WINSOCK.H(这个是WINSOCK API的头文件,WIN2K以上支持WINSOCK2,所以
可以用WINSOCK2.H);Ws2_32.lib(WINSOCK API连接库文件).
使用方式如下:
#include <winsock.h>
#pragma comment(lib,”ws2_32.lib”)

下面我们通过具体的代码演示服务器和客户端的工作流程:

首先,建立一个WSADATA结构,通常用wsaData
WSADATA wsaData;

然后,调用WSAStartup函数,这个函数是连接应用程序与winsock.dll的第一个调用.其中,第一个参数是WINSOCK 版本号,第二个参数是指向
WSADATA的指针.该函数返回一个INT型值,通过检查这个值来确定初始化是否成功.调用格式如下:WSAStartup(MAKEWORD(2,2),&wsaData),其中
MAKEWORD(2,2)表示使用WINSOCK2版本.wsaData用来存储系统传回的关于WINSOCK的资料.

if(iResuit=WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
{
printf(“WSAStartup failed:%d”,GetLastError());  //返回值不等与0,说明初始化失败
ExitProcess();                                  //退出程序
}

应用程序在完成对请求的SOCKET库使用后,要调用WSACleanup函数来接触SOCKET库的绑定,并且释放资源.

注意WSAStartup初始化后,必须建立一个SOCKET结构来保存SOCKET句柄.

下面我们建立一个SOCKET.

首先我们建立一个m_socket的SOCKET句柄,接着调用socket()函数,函数返回值保存在m_socket中.我们使用AF_INFE,SOCK_STREAM,IPPROTO_TCP
三个参数.第一个表示地址族,AF_INFE表示TCP/IP族,第二个表示服务类型,在WINSOCK2中,SOCKET支持以下三种类型;

SOCK_STREAM 流式套接字
SOCK_DGRAM  数据报套接字
SOCK_RAW    原始套接字

第三个参数表示协议:

IPPROTO_UDP  UDP协议 用于无连接数据报套接字
IPPROTO_TCP  TCP协议 用于流式套接字
IPPROTO_ICMP ICMP协议用于原始套接字

m_socket=socket(AF_INFE,SOCK_STREAM,IPPROTO_TCP);    //创建TCP协议

以下代码用于检查返回值是否有错误:

if(m_scoket==INVALID_SOCKET)
{
prinrf(“Error at socket():%d
“,GetLastError());
WSACleanup();                                   //释放资源
return;
}
说明,如果socket()调用失败,他将返回INVALID_SOCKET.

为了服务器能接受一个连接,他必须绑定一个网络地址,下面的代码展示如何绑定一个已经初始化的IP和端口的Socket.客户端程序用这个
IP地址和端口来连接服务器.

sockaddr_in service;
service.sin_family=AF_INET;                       //INTERNET地址族
service.sin_addr.s_addr=inet_addr(“127.0.0.1”);   //将要绑定的本地IP地址
service.sin_port=htons(27015);                     //27015将要绑定的端口

下面我们调用BIND函数,把SOCKET和SOCKADDR以参数的形式传入,并检查错误.

if(bind(m_socket,(SOCKADDR*)&SERVICE,sizeof(service))==SOCKET_ERROR)
{
printf(“bind() failed.
“);
closesocket(m_socket);
return;
}

当绑定完成后,服务器必须建立一个监听队列,以接受客户端的请求.listen()使服务器进入监听状态,该函数调用成功返回0,否则返回
SOCKET_ERROR.代码如下:

if(listen(m_socket,1)==SOCKET-ERROR)
{
printf(“error listening on socket.
“);
}

服务器端调用完LISTEN()后,如果此时客户端调用CONNECT()函数,服务器端必须在调用ACCEPT().这样服务器和客户端才算正式完成通信程序的
连接动作.

一旦服务器开始监听,我们就要指定一个句柄来表示利用ACCEPT()函数接受的连接,这个句柄是用来发送和接受数据的表示.建立一个SOCKET句柄
Socket AcceptSocket 然后利用无限循环来检测是否有连接传入.一但有连接请求,ACCEPT()函数就会被调用,并且返回这次连接的句柄.

printf(“waitong for a client to connect…
“);
while(1)
{
AcceptSocket=SOCKET_ERROR;
while(AcceptSocket==SOCKET_ERROR)
{
AcceptSocket=accept(m_socket,NULL,NULL);
}
}

下面看客户端端代码:

sockaddr_in clientService;
clientService.sin_family=AF_INET;                       //INTERNET地址族
clientService.sin_addr.s_addr=inet_addr(“127.0.0.1”);   //将要绑定的本地IP地址
clientService.sin_port=htons(27015);                     //27015将要绑定的端口

下面调用CONNECT()函数:

if ( connect( m_socket, (SOCKADDR*) &clientService, sizeof(clientService) ) == SOCKET_ERROR)
{
printf( “Failed to connect.
” );
WSACleanup();
return;
}                                                                     //如果调用失败清理退出
//调用成功继续读写数据

_________________________________________________________________________________________________
到这里,服务器和客户端的基本流程介绍完毕,下面我们介绍数据交换.

send():
int send
{
SOCKET s,                    //指定发送端套接字
const char FAR?*buf,         //指明一个存放应用程序要发送的数据的缓冲区
int len,                     //实际要发送的数据字节数
int flags                    //一般设置为0
};
C/S都用SEND函数向TCP连接的另一端发送数据.

recv():
int recv
{
SOCKET s,                    //指定发送端套接字
char FAR?*buf,              //指明一个缓冲区 存放RECC受到的数据
int len,                     //指明BUF的长度
int flags                    //一般设置为0

};
C/S都使用RECV函数从TCP连接的另一端接受数据

_______________________________________________________________________________________________

下面将完整的程序代码提供如下,大家可直接编译运行

首先看客户端的代码:

#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, “ws2_32.lib”)
void main() {

// 初始化 Winsock.
WSADATA wsaData;
int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
if ( iResult != NO_ERROR )
printf(“Error at WSAStartup()
“);

// 建立socket socket.
SOCKET client;
client = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );

if ( client == INVALID_SOCKET ) {
printf( “Error at socket(): %ld
“, WSAGetLastError() );
WSACleanup();
return;
}

// 连接到服务器.
sockaddr_in clientService;

clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr( “127.0.0.1” );
clientService.sin_port = htons( 27015 );

if ( connect( client, (SOCKADDR*) &clientService, sizeof(clientService) ) == SOCKET_ERROR) {
printf( “Failed to connect.
” );
WSACleanup();
return;
}

// 发送并接收数据.
int bytesSent;
int bytesRecv = SOCKET_ERROR;
char sendbuf[32] = “Client: Sending data.”;
char recvbuf[32] = “”;

bytesSent = send( client, sendbuf, strlen(sendbuf), 0 );
printf( “Bytes Sent: %ld
“, bytesSent );

while( bytesRecv == SOCKET_ERROR ) {
bytesRecv = recv( client, recvbuf, 32, 0 );
if ( bytesRecv == 0 || bytesRecv == WSAECONNRESET ) {
printf( “Connection Closed.
“);
break;
}
if (bytesRecv < 0)
return;
printf( “Bytes Recv: %ld
“, bytesRecv );
}

return;
}

下面是服务器端代码:

#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, “ws2_32.lib”)
void main() {

// 初始化
WSADATA wsaData;
int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
if ( iResult != NO_ERROR )
printf(“Error at WSAStartup()
“);

// 建立socket
SOCKET server;
server = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );

if ( server == INVALID_SOCKET ) {
printf( “Error at socket(): %ld
“, WSAGetLastError() );
WSACleanup();
return;
}

// 绑定socket
sockaddr_in service;

service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr( “127.0.0.1” );
service.sin_port = htons( 27015 );

if ( bind( server, (SOCKADDR*) &service, sizeof(service) ) == SOCKET_ERROR ) {
printf( “bind() failed.
” );
closesocket(server);
return;
}

// 监听 socket
if ( listen( server, 1 ) == SOCKET_ERROR )
printf( “Error listening on socket.
“);

// 接受连接
SOCKET AcceptSocket;

printf( “Waiting for a client to connect…
” );
while (1) {
AcceptSocket = SOCKET_ERROR;
while ( AcceptSocket == SOCKET_ERROR ) {
AcceptSocket = accept( server, NULL, NULL );
}
printf( “Client Connected.
“);
server = AcceptSocket;
break;
}

// 发送接受数据
int bytesSent;
int bytesRecv = SOCKET_ERROR;
char sendbuf[32] = “Server: Sending Data.”;
char recvbuf[32] = “”;

bytesRecv = recv( server, recvbuf, 32, 0 );
printf( “Bytes Recv: %ld
“, bytesRecv );

bytesSent = send( server, sendbuf, strlen(sendbuf), 0 );
printf( “Bytes Sent: %ld
“, bytesSent );

return;
}
本程序仅仅描述了同步的情况!

第五节 多线程编程介绍

对于多线程的基本概念,我不在赘述,是个只要学习过一门编程语言就应该多进程和线程有个基本的了解.这里重点介绍一下如何实现多线程.

通常一个程序的主线程有操作系统创建,如果想让其创建额外的线程,可以调用CreateThread()函数来完成.函数原形如下:

HANDLE CreateThread()
{
LPSECURITY_ATTRIBUTES LPThreadAttributes,  //指向SECURITY_ATTRIBUTES的指针
SIZE_T dwStackSize,                        //表示线程为自己所用堆栈分配的地址空间的大小 系统缺省值为0
LPTHREAD_START-TOUTINE lpStartAddress,     //表示新线程开始执行时代码所在函数的地址 即线程函数名
LPVOID lpParameter,                        //是传入线程函数的参数
DWORD dwCreationFlags,                     //指定控制线程创建的附加标志 取0线程立即执行 取CREATE_SUSPENDED线程挂起
LPDWORD lpThreadld                         //是个DWORD类型的地址,返回赋给该新线程的ID
}

线程函数lpParameter必须有以下原形:

DWORD WINAPI XXXThreadFun(LPVOID lpParameter)
{
return(0);
}

________________________________________________________________________________________________
下面我们来创建一个线程:

#include <windows.h>
#include <stdio.h>
DWORD WINAPI ThreadFunc( LPVOID lpParam )                         //线程函数,跟普通的函数没什么两样
{
printf( “Parameter = %d.”, *(DWORD*)lpParam );
return 0;
}

VOID main( VOID )
{
DWORD dwThreadId, dwThrdParam = 1;
HANDLE hThread;
hThread = CreateThread( NULL,0,ThreadFunc,&dwThrdParam, 0,&dwThreadId);
if (hThread == NULL)
{
printf( “CreateThread failed (%d)
“, GetLastError() );
}
else
{
_getch();
CloseHandle( hThread );
}
}

关于线程同步的问题,这里就不再讲解,请大家自己查阅资料,不查阅以后可能会有困难啊.培养一下各位的自己动手能力.

评论关闭。