基于C/S的TCP/IP协议的socket编程
TCP/IP协议簇的特点:面向连接的,可靠的,基于字节流的传输协议
UDP/IP协议簇的特点:面向不连接的,不可靠的基于数据报的传输层协议
C/S模型:客户端/服务端模型。根据概念层面的,实现层面可以是任何网络。
服务端:
1. 打开网络库
2.检验版本
3.创建socket
4.绑定地址与端口(bind)
5.开始监听
6.创建客户端socket(accept)
7.与客户机收发消息 (1)收(recv):得到客户端收到的参数 数据接收都是协议本身做的,
也就是socket的底层做的系统会有一段缓冲区,储存着接受的数据
recv的作用就是通过socket找到缓冲区,并将数据复制出来。
(2)发(send):向目标发送数据。send的作用就是通过socket找到缓冲区,并将数据粘贴出来。
客户端
1. 打开网络库
2.检验版本
3.创建socket
4.链接到服务器(connect)
7.与服务机收发消息 (1)收(recv):得到客户端收到的参数
(2)发(send):向目标发送数据
服务端
#include <stdio.h>
#include <stdlib.h>
#include<iostream>
#include <WinSock2.h>
using namespace std;
#pragma comment(lib,"Ws2_32.lib")
//标记
BOOL g_nFlag = TRUE;
int main(void)
{
//打开网络库
WORD wdVersion = MAKEWORD(1, 1);
WSADATA wsaData;
//int a = WSAStartup(wdVersion, &wsaData);
//printf("外%d\n", WSAStartup(wdVersion, &wsaData));
if (0 != WSAStartup(wdVersion, &wsaData)) // API
{
printf("WSAStartup fail!");
return -1;
}
//校验版本
if (1 != HIBYTE(wsaData.wVersion) || 1 != LOBYTE(wsaData.wVersion))
{
printf("Version fail!");
//关闭库
WSACleanup();
return -1;
}
//创建一个SOCKET 监听
SOCKET socketListen = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == socketListen)
{
printf("socket fail!");
//关闭库
WSACleanup();
return -1;
}
//第1个参数:地址的类型。AF_INET为ipv4地址 如:192.168.1.103。4字节 32位的地址,
//AF_INET6为ipv6地址,16字节128位的地址。
//第2个参数:套接字类型。SOCK_STREAM 一种套接字类型,
//提供带有OOB数据传输机制的顺序,可靠,双向,基于连接的字节流。
//此套接字类型使用TCP作为Internet地址系列(AF_INET或AF_INET6)
//SOCK_DGRAM 一种支持数据报的套接字类型,它是固定(通常最小)最大长度的无连接,不可靠的缓冲区。
//此套接字使用UDP作为Internet地址系列(AF_INET或AF_INET6)
//第3个参数:协议的类型。有IPPROTO_TCP,IPPROTO_UDP,IPPROTO_ICMP等。想要使用一个协议,
//需要设备支持才行,参数3可以填0,系统会帮我们自动选择协议类型
//绑定地址
SOCKADDR_IN sockAddress;
sockAddress.sin_family = AF_INET;
sockAddress.sin_addr.s_addr = 16777343;
sockAddress.sin_port = 12345;
//端口号的本质就是一个整数,0-65535。实际:介于0-1023,为系统保留占用端口号
//21端口分配给FTP(文件传输协议)服务。25端口分配给SMTP(简单邮件传输协议)服务。
//80端口分配给HTTP服务。443端口为HTTPS服务。我们不能写这个范围的,我们的范围为1024-65535。
if (SOCKET_ERROR == bind(socketListen, (struct sockaddr*) & sockAddress, sizeof(sockAddress)))
{
printf("bind fail!");
//int nError = ::WSAGetLastError();
//关闭库
closesocket(socketListen);
WSACleanup();
return -1;
}
//bind():参数1(SOCET s):上一个函数创建了socket,绑定了协议信息(地址类型,套接字类型,协议类型)
//参数2(const socketaddr *addr):结构体:地址类型。装IP地址,端口号
//参数3:(int namelen)参数2的类型大小, sizeof(参数2)
//返回值:成功返回0,失败返回SOCKET_ERROR(具体代码可以通过int WSAGetLastError(void)获得)
//开始监听
if (SOCKET_ERROR == listen(socketListen, 2))
{
cout << "listen fail!" << endl;
//关闭库
closesocket(socketListen);
WSACleanup();
return -1;
}
//接受链接
SOCKADDR_IN sockClient;
int nLen = sizeof(sockClient);
SOCKET newSocket;
newSocket = accept(socketListen, NULL, NULL);
//getsockname(socketListen, (struct sockaddr*)&sockClient, &nLen);
if (INVALID_SOCKET == newSocket)
{
printf("listen fail!");
//关闭库
closesocket(socketListen);
WSACleanup();
return -1;
}
//SOCKET socketclient=accept(socketserver, NULL,NULL);
//也可以实现,我们得到的东西不需要接收,我们直接使用socketclient。
//想要得到信息可以使用:getpeername(socketclient,(sockaddr*)&clientMsg,&len);
//即可以得到socketclient的clientMsg和len信息。
//getsockname(socketserver, (sockaddr *)&clientMsg,&len) 得到本地服务器信息
//参数1(SOCKET s):服务器端的socket,也就是socket函数创建的
//参数2(sockaddr *addr):客户端的地址端口信息结构体(和bind的第二个参数一样,
//意义:系统帮我们监视着客户端的信息,也就是IP地址和端口号,并通过这个结构体帮我们记录)
//参数3(int *addrlen):参数2的大小 sizeof。
//返回值:成功 返回值就是客户端包好的socket(与客户端通信就靠这个)。
//失败 返回INVALID_SOCKET (具体代码可以通过int WSAGetLastError(void)获得)
while (g_nFlag)
{
//判断客户端连接的集合中是否有需要接收的数据
char szRecvBuffer[1400] = { 0 };
char szSendBuffer[1024];
//遍历setClient集合,如果发现setClient中的某个
int nReturnValue = recv(newSocket, szRecvBuffer, sizeof(szRecvBuffer) - 1, 0);
int nRes = WSAGetLastError();
if (0 == nReturnValue)
{
//客户端正常关闭 服务端释放Socket
continue;
}
else if (SOCKET_ERROR == nReturnValue)
{
//网络中断
printf("客户端中断连接");
continue;
}
else
{
//接收到客户端消息
cout << "Client Data :" << szRecvBuffer << endl;
//recv():参数1(SOCKET s):客户端的socket,每个客户端对应唯一的socket
//参数2(char* buf):客户端消息的存储空间,也就是字符数组 这个一般1500字(网络传输的最大单元)
//参数3(int len):想要读取的字节个数 一般是参数2的字节数-1,把\0字符串结尾留出来
//参数4(int flags):数据的读取方式,填0(从系统缓冲区读出来,系统删除数据),
//还有MSG_PEEK(从系统缓冲区读出来,系统不删除)。
//返回值 读取字节数的大小,读没了
//给客户回信
//scanf_s ("%s", szSendBuffer, 1024);
//getchar();
//send(newSocket, "repeat over", strlen(szSendBuffer)+1, 0);
//send():参数1:目标的socket,每个客户端对应唯一的socket
//参数2:给对方发送的字节串(这个不要超过1500字节)
//参数3:字节个数 1400
//参数4:填0
//返回值 成功返回写入的字节数 执行失败 返回SOCKET_ERROR(处理:重启,等待,不用理会)
}
}
//关闭socket
closesocket(socketListen);
closesocket(newSocket);
//关闭网络库
WSACleanup();
//system("pause");
return 0;
}
客户端
#include<winsock2.h>
#include<string>
#include<iostream>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int main()
{
WORD wdVersion = MAKEWORD(2, 2);//版本2-2
WSADATA wdSockMsg;
int nRes = WSAStartup(wdVersion, &wdSockMsg);//打开网络库
if (0 != nRes)
{
switch (nRes)
{
case WSASYSNOTREADY:
cout << "底层网络子系统尚未准备好进行网络通信。" << endl;
break;
}
}
SOCKET socketserver = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//服务器socket
if(INVALID_SOCKET== socketserver)
{
int a = WSAGetLastError();
cout<< "创建socket失败" << endl;
WSACleanup();
return 0;
}
sockaddr_in serverMsg;
serverMsg.sin_family = AF_INET;
serverMsg.sin_port = 12345;
serverMsg.sin_addr.S_un.S_addr =16777343;
int bres = connect(socketserver, (sockaddr*)& serverMsg, sizeof(serverMsg));//链接服务器
if (SOCKET_ERROR == bres)
{
cout << "链接客户端失败" << endl;
int a = WSAGetLastError();
closesocket(socketserver);
WSACleanup();
return 0;
}
while (1)
{
char szSendData[1024];
cout << "Input Something:" << endl;
cin >> szSendData;
if (SOCKET_ERROR == send(socketserver, szSendData, sizeof(szSendData), 0))
{
cout << "send fail!" << endl;
closesocket(socketserver);
WSACleanup();
return 0;
}
}
//char buff[1500] = { 0 };
//int res = recv(socketserver, buff, 1499, 0);
//if (0 == res)
//{
// cout << "客户端下线,链接中断" << endl;
//}
//else if (SOCKET_ERROR == res)
//{
// int a = WSAGetLastError();
// cout << "出错了!" << endl;//根据错误码信息做相应处理(重启,等待,不用理会)
//}
//else
//{
// cout << res << " " << buff << endl;
//}
closesocket(socketserver);//关闭套接字。
WSACleanup();//清理网络库
system("pause");
return 0;
}