网络基础与Tcp编程
windows中,要想进行socket网络操作,
必须包含一个名叫做WinSock2.h(或者WinSock.h),
如果包含的是WinSock2.h则必须在windows.h之前,
包含完头文件之后,还要链接一个库文件ws2_32.lib,
先来看服务端。
首先,要进行网络操作,
我们先要进行一下网络环境的初始化。
WSAStartup函数就是用来初始化网络环境的。
int WSAStartup (
WORD wVersionRequested,
LPWSADATA lpWSAData
) ;
函数的第二个参数,
接收一个WSAData结构的指针,
该结构呢,里边包含了版本号,
我们传递的版本号会对该结构里边的版本号进行初始化。
初始化完成之后,我们需要创建一个socket(套接字)
SOCKET socket (
int af,
int type,
int protocol
) ;
其实,socket也是一个内核对象,但是它没有内核对象所拥有的明显标志,安全属性。
创建好套接字后呢,我们需要告诉操作系统需要在哪个地址和端口上进行网络操作,
相当于管道通信中绑定到标准输入输出口上。绑定的时候,需要有一个SOCKADDR_IN这个结构体,
struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[ 8 ] ;
} ;
初始化完端口,地址等信息后,需要调用bind函数,来完成绑定操作,
int bind (
SOCKET s,
const struct sockaddr FAR * name,
int namelen
) ;
绑定之后,我们还需要调用listen函数来进行监听操作,这个操作呢,
就相当于门卫一样了,如果有人来,就告诉你一声,这就是监听。
int listen (
SOCKET s,
int backlog
) ;
监听完成之后,我们就可以进行接收客户端的连接了,我们需要调用accept这个函数来进行接客。
SOCKET accept (
SOCKET s,
struct sockaddr FAR * addr,
int FAR * addrlen
) ;
接完客之后,我们就可以进行通信了,需要调用recv和send两个函数来进行收发数据,
int recv (
SOCKET s,
char FAR * buf,
int len,
int flags
) ;
int send (
SOCKET s,
const char FAR * buf,
int len,
int flags
) ;
当我们传输完数据后,应该调用WSACleanup和closesocket来进行关闭网络环境和套接字。
int WSACleanup ( void ) ;
int closesocket (
SOCKET s
) ;
服务端的示例代码:
#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>
#pragma comment(lib,"ws2_32.lib")
#define PORT 6000
DWORD WINAPI clientProc ( LPARAM lparam)
{
SOCKET sockClient = ( SOCKET) lparam;
char buf[ 1024 ] ;
while ( TRUE)
{
memset ( buf, 0 , sizeof ( buf) ) ;
int ret = recv ( sockClient, buf, sizeof ( buf) , 0 ) ;
if ( SOCKET_ERROR == ret)
{
printf ( "socket recv failed\n" ) ;
closesocket ( sockClient) ;
return - 1 ;
}
if ( ret == 0 )
{
printf ( "client close connection\n" ) ;
closesocket ( sockClient) ;
return - 1 ;
}
ret = send ( sockClient, buf, strlen ( buf) , 0 ) ;
if ( SOCKET_ERROR == ret)
{
printf ( "socket send failed\n" ) ;
closesocket ( sockClient) ;
return - 1 ;
}
}
closesocket ( sockClient) ;
return 0 ;
}
bool InitNetEnv ( )
{
WSADATA wsa;
if ( WSAStartup ( MAKEWORD ( 2 , 2 ) , & wsa) != 0 )
{
printf ( "WSAStartup failed\n" ) ;
return false;
}
return true;
}
int main ( int argc, char * argv[ ] )
{
if ( ! InitNetEnv ( ) )
{
return - 1 ;
}
SOCKET sServer = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP) ;
if ( sServer == INVALID_SOCKET)
{
printf ( "socket failed\n" ) ;
return - 1 ;
}
printf ( "Create socket OK\n" ) ;
SOCKADDR_IN addrServ;
addrServ. sin_family = AF_INET;
addrServ. sin_port = htons ( PORT) ;
addrServ. sin_addr. S_un. S_addr = INADDR_ANY;
int ret = bind ( sServer, ( sockaddr * ) & addrServ, sizeof ( sockaddr) ) ;
if ( SOCKET_ERROR == ret)
{
printf ( "socket bind failed\n" ) ;
WSACleanup ( ) ;
closesocket ( sServer) ;
return - 1 ;
}
printf ( "socket bind OK\n" ) ;
ret = listen ( sServer, 10 ) ;
if ( SOCKET_ERROR == ret)
{
printf ( "socket listen failed\n" ) ;
WSACleanup ( ) ;
closesocket ( sServer) ;
return - 1 ;
}
printf ( "socket listen OK\n" ) ;
sockaddr_in addrClient;
int addrClientLen = sizeof ( sockaddr_in) ;
while ( TRUE)
{
SOCKET * sClient = new SOCKET;
* sClient= accept ( sServer, ( sockaddr* ) & addrClient, & addrClientLen) ;
if ( INVALID_SOCKET == * sClient)
{
printf ( "socket accept failed\n" ) ;
WSACleanup ( ) ;
closesocket ( sServer) ;
delete sClient;
return - 1 ;
}
CreateThread ( 0 , 0 , ( LPTHREAD_START_ROUTINE) clientProc, ( LPVOID) * sClient, 0 , 0 ) ;
}
closesocket ( sServer) ;
WSACleanup ( ) ;
return 0 ;
}
接下来,看下客户端。
客户端比较简单,前面的部分和服务端都基本相同,在绑定操作上会有所差别。
客户端呢,需要调用connect函数
int connect (
SOCKET s,
const struct sockaddr FAR * name,
int namelen
) ;
连接成功后,就可以和服务端进行通信了,调用recv和send来进行收发数据。
当通信完之后,就可以关闭连接了。
当客户端和服务端刚开始连接的时候呢,两者会先进行沟通,
这个沟通需要3个步骤来完成,我们称之为3次握手,
同样的关闭连接的时候,需要进行4个步骤来完成,我们称之为4次握手。
#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>
#pragma comment(lib,"ws2_32.lib")
#define PORT 6000
int main ( int argc, char * argv[ ] )
{
WSADATA wsa;
if ( WSAStartup ( MAKEWORD ( 2 , 2 ) , & wsa) != 0 )
{
printf ( "WSAStartup failed\n" ) ;
return - 1 ;
}
SOCKET sServer = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP) ;
if ( sServer == INVALID_SOCKET)
{
printf ( "socket failed\n" ) ;
return - 1 ;
}
SOCKADDR_IN addrServ;
addrServ. sin_family = AF_INET;
addrServ. sin_port = htons ( PORT) ;
addrServ. sin_addr. S_un. S_addr = inet_addr ( "127.0.0.1" ) ;
int ret = connect ( sServer, ( SOCKADDR* ) & addrServ, sizeof ( SOCKADDR) ) ;
if ( SOCKET_ERROR == ret)
{
printf ( "socket connect failed\n" ) ;
WSACleanup ( ) ;
closesocket ( sServer) ;
return - 1 ;
}
char szBuf[ 1024 ] ;
memset ( szBuf, 0 , sizeof ( szBuf) ) ;
sprintf_s ( szBuf, sizeof ( szBuf) , "Hello server" ) ;
ret = send ( sServer, szBuf, strlen ( szBuf) , 0 ) ;
if ( SOCKET_ERROR == ret)
{
printf ( "socket send failed\n" ) ;
closesocket ( sServer) ;
return - 1 ;
}
ret = recv ( sServer, szBuf, sizeof ( szBuf) , 0 ) ;
if ( SOCKET_ERROR == ret)
{
printf ( "socket recv failed\n" ) ;
closesocket ( sServer) ;
return - 1 ;
}
printf ( "%s\n" , szBuf) ;
closesocket ( sServer) ;
WSACleanup ( ) ;
return 0 ;
}
Udp
TCP创建一个socket调用socket函数时,
第二个参数为SOCK_STREAM,
而UDP则需要给定一个SOCK_DGRAM,
然后在第三个参数上给一个IPPROTO_UDP,
这样我们就创建好了一个UDP的socket。
接下来,也和TCP一样,指定SOCKADDR_IN的地址信息(端口,ip),
指定完之后呢,若是客户端,则可以直接就进行通信了,
若是服务端,则还需要增加一步bind操作,
当我们调用bind函数,进行绑定后,服务端就可以和客户端进行通信了。
上篇提到TCP进行数据的收发是通过recv和send两个API来进行数据的收发的。
而UDP也需要两个函数,叫做recvform和sendto,这两个和TCP那两个有点不同,
int recvfrom (
SOCKET s,
char FAR* buf,
int len,
int flags,
struct sockaddr FAR * from,
int FAR * fromlen
) ;
int sendto (
SOCKET s,
const char FAR * buf,
int len,
int flags,
const struct sockaddr FAR * to,
int tolen
) ;
注意,这两个函数里边有一个sockaddr结构地址,它是用来保存该数据发送者的信息的。
UDP是面向数据包的,因此就好像寄快递一样,你必须在快递上写一张纸条,
上面填好姓名,地址等信息,填好之后,接收者才知道该东西是由谁寄过来的。
因此,上面两个函数提供了sockaddr结构的地址,用于保存从哪里发来的和发送到哪里的地址信息。
服务端:
#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>
#pragma comment(lib,"ws2_32.lib")
#define PORT 6000
int main ( int argc, char * argv[ ] )
{
WSADATA wsa;
if ( WSAStartup ( MAKEWORD ( 2 , 2 ) , & wsa) != 0 )
{
printf ( "WSAStartup failed\n" ) ;
return - 1 ;
}
SOCKET sock = socket ( AF_INET, SOCK_DGRAM, IPPROTO_UDP) ;
if ( sock == SOCKET_ERROR)
{
printf ( "create socket failed\n" ) ;
return - 1 ;
}
sockaddr_in serverAddr;
serverAddr. sin_family = AF_INET;
serverAddr. sin_port = htons ( PORT) ;
serverAddr. sin_addr. S_un. S_addr = htonl ( INADDR_ANY) ;
bind ( sock, ( sockaddr* ) & serverAddr, sizeof ( sockaddr) ) ;
char buf[ 512 ] ;
while ( TRUE)
{
memset ( buf, 0 , 512 ) ;
sockaddr_in clientAddr;
memset ( & clientAddr, 0 , sizeof ( sockaddr_in) ) ;
int clientAddrLen = sizeof ( sockaddr) ;
int ret = recvfrom (
sock,
buf,
512 ,
0 ,
( sockaddr* ) & clientAddr,
& clientAddrLen ) ;
printf (
"Recv msg:%s from IP:[%s] Port:[%d]\n" ,
buf,
inet_ntoa ( clientAddr. sin_addr) ,
ntohs ( clientAddr. sin_port) ) ;
sendto (
sock,
"Hello World!" ,
strlen ( "Hello World!" ) ,
0 ,
( sockaddr* ) & clientAddr,
clientAddrLen) ;
printf (
"Send msg back to IP:[%s] Port:[%d]\n" ,
inet_ntoa ( clientAddr. sin_addr) ,
ntohs ( clientAddr. sin_port) ) ;
}
return 0 ;
}
客户端:
#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>
#pragma comment(lib,"ws2_32.lib")
#define PORT 6000
int main ( int argc, char * argv[ ] )
{
WSADATA wsa;
if ( WSAStartup ( MAKEWORD ( 2 , 2 ) , & wsa) != 0 )
{
printf ( "WSAStartup failed\n" ) ;
return - 1 ;
}
SOCKET sockClient = socket ( AF_INET, SOCK_DGRAM, IPPROTO_UDP) ;
if ( sockClient == INVALID_SOCKET)
{
printf ( "create socket failed\n" ) ;
return - 1 ;
}
sockaddr_in addr = { 0 } ;
addr. sin_family = AF_INET;
addr. sin_port = htons ( PORT) ;
addr. sin_addr. S_un. S_addr = inet_addr ( "127.0.0.1" ) ;
char buf[ ] = "client test!" ;
int dwSent = sendto (
sockClient,
buf,
strlen ( buf) ,
0 ,
( SOCKADDR * ) & addr,
sizeof ( SOCKADDR) ) ;
if ( dwSent == 0 )
{
printf ( "send %s failed\n" , buf) ;
return - 1 ;
}
printf ( "send msg:%s\n" , buf) ;
char recvBuf[ 512 ] ;
memset ( recvBuf, 0 , 512 ) ;
sockaddr_in addrSever = { 0 } ;
int nServerAddrLen= sizeof ( sockaddr_in) ;
int dwRecv = recvfrom (
sockClient,
recvBuf,
512 ,
0 ,
( SOCKADDR * ) & addrSever,
& nServerAddrLen) ;
printf ( "Recv msg from server : %s\n" , recvBuf) ;
closesocket ( sockClient) ;
WSACleanup ( ) ;
system ( "pause" ) ;
return 0 ;
}
参考
https://blog.csdn.net/Timmiy/article/details/51946093