WinSock网络编程基础(2)客户端

接下来说一下如何用WinSock创建基于TCP/IP模型的客户端和服务器。

TCP可以提供两个计算机间可靠无误的数据传输,应用程序使用TCP通信时,会在两台计算机之间建立一个虚拟连接,连接之后计算机之间变可以以双向字节流进行数据交换。

下面说下简单的发送数据的客户端实现.

创建客户机的连接比较简单:

1.创建一个套接字,定义addrinfo对象并初始化这些值(该对象包含一个sockaddr结构)

struct addrinfo *result = NULL,
                *ptr = NULL,
                hints;

ZeroMemory( &hints, sizeof(hints) );
hints.ai_family = AF_UNSPEC;  //可以是IPv4或IPv6地址
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;

调用getaddrinfo函数确定服务器ip地址(由命令行参数传递)和端口

#define DEFAULT_PORT "27015"

// Resolve the server address and port
iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
    printf("getaddrinfo failed: %d\n", iResult);
    WSACleanup();
    return 1;
}

socket原型:

socket(

IN int af,       //协议的地址族,使用IPv4来描述Winsock,设置为AF_INET

IN int type,     //套接字类型,TCP/IP设置为SOCK_STREAM,UDP/IP设置为SOCK_DGRAM

IN int protocol      //用于给定地址族和类型具有多重入口的传送限定,TCP设置为IPPROTO_TCP,UDP设置为IPPROTO_UDP

);

调用socket创建嵌套字,错误检测

SOCKET ConnectSocket = INVALID_SOCKET;
ptr=result;
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
    ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
    printf("Error at socket(): %ld\n", WSAGetLastError());
    freeaddrinfo(result);
    WSACleanup();
    return 1;
}

2.连接到的服务器名。(在TCP/IP中就是监听服务器的IP地址和端口号)

iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
    closesocket(ConnectSocket);
    ConnectSocket = INVALID_SOCKET;
}
//释放资源
freeaddrinfo(result);

if (ConnectSocket == INVALID_SOCKET) {
    printf("Unable to connect to server!\n");
    WSACleanup();
    return 1;
}

客户端程序在创建套节字之后,要使用 connect函数请求与服务器连接,connect原型:

int WSAAPI connect(

IN SOCKET s,            //要建立连接的socket

IN const struct sockaddr FAR * name,    //指向保存要建立连接信息的地址结构

IN int namelen          //参数2指向地址结构的大小

);

3.发送和接收数据。

收发数据才是网络编程的主题,在已经建立连接的套接字上发生数据可以使用send或WSASend(WinSock2中),接受可用recv和WSARecv。收发数据都是用char类型(面向字节的数据)。

#define DEFAULT_BUFLEN 512

int recvbuflen = DEFAULT_BUFLEN;

char *sendbuf = "this is a test";
char recvbuf[DEFAULT_BUFLEN];

int iResult;

iResult = send(ConnectSocket, sendbuf, (int) strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
    printf("send failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

printf("Bytes Sent: %ld\n", iResult);

iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

do {
    iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
    if (iResult > 0)
        printf("Bytes received: %d\n", iResult);
    else if (iResult == 0)
        printf("Connection closed\n");
    else
        printf("recv failed: %d\n", WSAGetLastError());
} while (iResult > 0);

所有收发数据返回的错误代码都是SOCKET_ERROR,一旦有错误返回,系统就会调用WSAGetLastError()获取详细错误信息。

常见错误:

WSAECONNABORTED和WSAECONNRESET:连接正在关闭(超时或者由于通信放正在关闭连接)
WSAEWOULDBLOCK:套接字处于非阻塞模式或异步状态。

使用send和recv函数原型。

int send(

SOCKET s, // 套节字句柄

const char FAR* buf,// 要发送的数据的缓冲区的地址

int len, // 缓冲区的长度

int flags  // 指定了的调用方式,通常设为0

);

int recv(

SOCKET s,

char FAR* buf,

int len,

int flags

);

send函数在一个连接的套节字上发送缓冲区内的数据,返回发送数据的实际字节数,recv函数从对方接收数据,并存储它到指定的缓冲区。flag参数在这两个函数中通常设为0。

在阻塞模式下,send将会阻塞线程的执行直到所有的数据发送完毕(或者一个错误的发生),而recv函数将返回尽可能多的当前信息,一直到缓冲区指定的大小。

函数执行失败返回INVALID_SOCKET(-1),应该调用closesocket函数将它关闭。如果没有错误发生,函数返回0,否则返回SOCKET_ERROR。函数用法如下:

int closesocket(

__in  SOCKET s // 函数唯一的参数就是要关闭的套节字的句柄

);

4.断开连接,关闭套接字(当客户机已经发出数据,可以使用shutdown函数和SD_SEND宏关闭发送套接字,客户机仍然允许接受来自服务器套接字上的数据)

iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}
closesocket(ConnectSocket);
WSACleanup();

附完整的客户端代码:

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <IPHlpApi.h>
#include <stdio.h>

#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Mswsock.lib")
#pragma comment(lib, "Advapi32.lib")

#define DEFAULT_PORT "27015"
#define DeFAULT_BUFLEN 512

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	int iResult;
	struct addrinfo *result = NULL,
					*ptr = NULL,
					hints;

	char *sendbuf = "this is a test";
	char recvbuf[DeFAULT_BUFLEN];
	int recvbuflen = DeFAULT_BUFLEN;
	SOCKET ConnectSocket = INVALID_SOCKET;

	if (argc != 2)
	{
		printf("usage: %s server-name\n", argv[0]);
	}

	// 初始化winsock
	iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (iResult != 0)
	{
		printf("WSAStartup failed: %d\n", iResult);
		return 1;
	}

	ZeroMemory(&hints, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	// 确定服务器地址和端口
	iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
	if (iResult != 0)
	{
		printf("getaddrinfo failed: %d\n", iResult);
		WSACleanup();
		return 1;
	}

	// 尝试连接到服务器地址,直到成功
	for (ptr = result; ptr != NULL; ptr = ptr->ai_next)
	{
		// 创建套接字连接到服务器
		ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
		if (ConnectSocket == INVALID_SOCKET)
		{
			printf("Error at socket(): %ld\n", WSAGetLastError());
			freeaddrinfo(result);
			WSACleanup();
			return 1;
		}
		// 连接服务器
		iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
		if (iResult == SOCKET_ERROR)
		{
			closesocket(ConnectSocket);
			ConnectSocket = INVALID_SOCKET;
			continue;
		}
		break;
	}

	// 释放资源
	freeaddrinfo(result);
	if (ConnectSocket == INVALID_SOCKET)
	{
		printf("Unable to connect to server!\n");
		WSACleanup();
		return 1;
	}

	// 发送sendbuf内容
	iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf) + 1, 0);
	if (iResult == SOCKET_ERROR)
	{
		printf("send failed:%d\n", WSAGetLastError());
		closesocket(ConnectSocket);
		WSACleanup();
		return 1;
	}
	printf("Byte Send:%ld\n", iResult);

	// 数据发送完成之后断开连接
	iResult = shutdown(ConnectSocket, SD_SEND);
	if (iResult == SOCKET_ERROR)
	{
		printf("shutdown faild: %d\n", WSAGetLastError());
		closesocket(ConnectSocket);
		WSACleanup();
		return 1;
	}

	// 收到回应之后关闭连接
	do
	{
		iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
		if (iResult > 0)
			printf("Byte received: %d\n", iResult);
		else if (iResult == 0)
			printf("connection closed\n");
		else
			printf("recv failed: %d\n", WSACleanup());

	} while (iResult > 0);

	// 清除套接字
	closesocket(ConnectSocket);
	WSACleanup();

	return 0;
}

  本文链接: http://www.bugcoding.com/entry/10

时间: 2024-08-04 13:48:46

WinSock网络编程基础(2)客户端的相关文章

WinSock网络编程基础(1)

记录学习windows网络编程过程中遇到的问题和相关笔记 基本概念: Socket: socket起源于UNIX,Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.基于"打开—读/写—关闭"模式,其中WinSock就是变种之一 以下是一个典型的C/S中使用套接字接口的概述: WinSock: WinSock是Windows平台下的网络编程接口,它是基于Unix下的BSD Socket发展而来,是一个与网络协议无关的编程接口. WinSock目前有两个版本: W

WinSock网络编程基础(3)server

上一篇讲的是简单的发送数据的客户端的实现.接下来讲的是如何实现收发数据服务器.这里说的服务器其实就是一个进程,它需要等待任意数量的客户端与之建立起连接,以便响应它们的请求. 服务器必须在已知的名称上监听连接(在TCP/IP中是ip地址和端口号,不同协议的寻址方案和命名方法也不同) 创建服务器程序的第一步和客户端一样,要先初始化并且创建SOCKET(LISTENSOCKET用于监听客户端连接) #define DEFAULT_PORT "27015" struct addrinfo *r

python全栈开发从入门到放弃之socket网络编程基础

网络编程基础 一 客户端/服务器架构 1.硬件C/S架构(打印机) 2.软件C/S架构 互联网中处处是C/S架构 如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种) 腾讯作为服务端为你提供视频,你得下个腾讯视频客户端才能看它的视频) C/S架构与socket的关系: 我们学习socket就是为了完成C/S架构的开发 为何学习socket一定要先学习互联网协议: 1.首先:本节课程的目标就是教会你如何基于socket编程,来开发一款自己的C/S架构软件 2.其次:C/S架构的

网络编程基础

网络编程基础 1.套接字概念 Linux环境下使用套接字进行进程之间的通信.用过套接字的接口,其他进程的位置对于应用程序来讲是透明的.相互通信双方端点都有一个套接字,双方如果要进行通信,通过套接字建立桥梁,双方就可以通信了. 类似文件一样,套接字也有一个套接字描述符,应用程序可以像操作文件一样操作套接字.在进行网络通信的过程中,用户感觉就是在操作文件一样,这是Linux将外部设备抽象为一个文件的好处. 2.字节序 不同主机的体系结构不同,所采用的数据存储方式不同.网络中,进程之间的通信是跨主机的

iOS开发网络篇—网络编程基础

iOS开发网络篇—网络编程基础 一.为什么要学习网络编程 1.简单说明 在移动互联网时代,移动应用的特征有: (1)几乎所有应用都需要用到网络,比如QQ.微博.网易新闻.优酷.百度地图 (2)只有通过网络跟外界进行数据交互.数据更新,应用才能保持新鲜.活力 (3)如果没有了网络,也就缺少了数据变化,无论外观多么华丽,终将变成一潭死水 移动网络应用 = 良好的UI + 良好的用户体验 + 实时更新的数据 新闻:网易新闻.新浪新闻.搜狐新闻.腾讯新闻 视频:优酷.百度视频.搜狐视频.爱奇艺视频 音乐

Java网络编程基础(六)— 基于TCP的NIO简单聊天系统

在Java网络编程基础(四)中提到了基于Socket的TCP/IP简单聊天系统实现了一个多客户端之间护法消息的简单聊天系统.其服务端采用了多线程来处理多个客户端的消息发送,并转发给目的用户.但是由于它是基于Socket的,因此是阻塞的. 本节我们将通过SocketChannel和ServerSocketChannel来实现同样的功能. 1.客户端输入消息的格式 username:msg    username表示要发送的的用户名,msg为发送内容,以冒号分割 2.实现思路 实现思路与Java网络

Java网络编程基础【转】

网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编程是比较复杂的系统工程,需要了解很多和网络相关的基础知识,其实这些都不是很必需的.首先来问一个问题:你 会打手机吗?很多人可能说肯定会啊,不就是按按电话号码,拨打电话嘛,很简单的事情啊!其实初学者如果入门网络编程的话也可以做到这么简单! 网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据.

嵌入式 Linux网络编程(一)——Socket网络编程基础

嵌入式 Linux网络编程一--Socket网络编程基础 一.Socket简介 1.网络中进程间通信 本机进程使用进程号区别不同的进程进程间通信方式有管道.信号.消息队列.共享内存.信号量等.网络中进程间的通信首先需要识别进程所在主机在网络中的唯一标识即网络层的IP地址主机上的进程可以通过传输层的协议与端口号识别. 2.Socket原理 Socket是应用层与TCP/IP协议族通信的中间软件抽象层是一种编程接口.Socket屏蔽了不同网络协议的差异支持面向连接(Transmission Cont

Linux网络编程------网络编程基础

Socket(套接字),类似文件描述符,三种 1.流式套接字(SOCK_STREAM):可以提供可靠的.面向连接的通讯流,它使用TCP协议.TCP保证了数据传输的正确性和顺序性. 2.数据报套接字(SOCK_DGRAM):定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错,它使用数据报协议(UDP). 3.原始套接字(SOCK_RAW):直接基于IP协议. 网络地址 struct sockaddr用于记录网络地址: struct sockaddr { u_s