本系列基于哈工大李全龙老师课程
目录
2.1操作系统可能会有多个应用在通信每个应用也会有多个进程进行通信,那么如何区分呢?
下面将会带你去了解什么是Socket?Socket是干嘛的?以及原理和Socket的简单实践
一.什么是Socket?Socket存在于什么地方?
1.1介绍:
Socket,又叫套接字,是一种用于网络通信的编程接口,它提供了一种机制,使得不同计算机之间可以通过网络进行通信。通过Socket,可以在不同计算机之间建立连接,并在连接上进行数据的传输。Socket可以用于不同的网络协议,例如TCP/IP、UDP等。在Socket编程中,通常有一个服务器端和一个或多个客户端,服务器端负责监听和接受客户端的连接请求,而客户端则负责发起连接请求并进行数据的发送和接收。通过Socket,可以实现各种网络通信应用,例如网页浏览、文件传输、聊天等。
简短一点:Socket是一种用于网络通信的编程接口,它允许不同计算机之间建立连接并进行数据传输。
大白话一点就是:Socket就是一个工具,使用Socket可以用来让不同的计算机之间可以进行信息交换。
1.2:Socket存在于哪里?作用是啥?
贴一张五层协议中各个协议的应用:
由此可见啊,Socket存在于应用层和其下面的传输层之间!
那么5层协议的最顶层—应用层是干嘛的?
应用层主要负责处理特定应用程序的通信需求和数据交换。应用层协议定义了应用程序之间的通信规则和数据格式。
那传输层是干嘛的?
传输层主要负责提供可靠的端到端数据传输服务,确保数据的完整性、可靠性和顺序性。它实现了数据的分段和重组、端口管理、连接管理、流量控制和拥塞控制等功能。传输层还可以进行错误检测和纠正,以确保数据的准确性。
总结就是应用层中的应用需要对外通信,然后传输层就是把这些应用发送的数据进行传输发送。但是问题来了,应用层发送的数据到了传输层后是怎么进行发送和处理的呢?
说明:
在应用层,应用程序通过Socket API调用创建一个Socket对象。Socket对象封装了传输层协议的细节,提供了一组方法和属性,用于在应用程序和网络之间传输数据。
在传输层,Socket将应用程序的数据分成合适的大小,进行分段和重组,添加必要的控制信息(如序号、端口号等),并通过网络传输到目标主机。
总结起来,Socket在应用层和传输层之间发挥作用,通过封装传输层协议的细节,提供了一种通信机制,使应用程序能够与网络进行数据交换。
抽象起来就是:服务器创建一下Socket,客户端也创建一个Socket,服务器先运行,客户端后运行。接着客户端就像插头,服务器就像插座,客户端Socket”插入“服务器的Socket后进行通信。
API:
几种典型的应用编程接口:
二.WinSocket这个工具提供的API
2.1操作系统可能会有多个应用在通信每个应用也会有多个进程进行通信,那么如何区分呢?
可以给服务器创建的Socket绑定一个端口号(16位整数),客户端创建了Socket后对外:采用IP地址+端口号可以找到服务器地址和服务器上创建的具体哪一个Socket。对内:操作系统使用套接字描述符来管理套接字。类似于文件的抽象:
2.2API函数(WinSock)
WSAStartup:
WSACleanup:
socket:
Closesocket:
bind:
地址通配符:本机的任意一个有效的IP地址都可以
listen:
connect:
accept:
为什么会创建一个新的套接字来于客户端连接呢?
因为TCP是面向连接的可靠的有序的字节流传输协议,并且是点对点的意味着一个TCP连接只能连接客户端和服务器端这两个套接字。这样做可以实现并发的TCP连接。
send:
recv:
小结:
关于网络字节顺序(也就是表示层的作用:本地字节和网络字节顺序的相互转换):
★★Socket API(TCP)调用的基本流程!!(看懂流程就知道如何写代码):
三:简单实践:用Socket实现服务器和客户端
3.1服务器端的Socket设计
首先,初始化网络库:版本2.2
接着创建Socket:创建并绑定好socket后开始监听并将其返回
等待客户端连接:与客户端的connect相互作用
如果连接成功发出“已连接服务器”:
不停的接收客户端的消息:这里我用while来实现,将收到的消息储存在recvBuf中,并判断是否客户端下线,如果没有下线就把收到的消息打印下来。
最后WSACleanup函数会关闭所有已打开的套接字、释放分配的内存和其他资源,以及终止Winsock库的使用。:
3.2客户端的Socket设计
首先,初始化网络库,这里和服务器一样。
接着创建客户端的套接字,和服务器不同的是,客户端的套接字要绑定服务器端的IP地址
这里用本地回环地址来作为服务器IP地址,并于服务器进行连接
然后接受服务器发来的消息,用recv函数,将服务器发送的消息保存在recvBuf中
使用send函数开始与服务器进行通信,这里使用while实现多条信息的发送,并将输入的消息保存在sendBuf里,与“end”比较
如果客户端不下线,就把消息发送给服务器,服务器接收(while)并打印。
服务器端:
最后WSACleanup函数会关闭所有已打开的套接字、释放分配的内存和其他资源,以及终止Winsock库的使用。,与服务器一样。
3.3完整代码:
结构:箭头表示服务器和客户端共用的代码
//TCPSocket.cpp
//下面的代码有很大一部分是服务器和客户端都会用到的代码所以我单独弄成了一个cpp
#include "TcpSocket.h"
bool init_Socket()
{
WSADATA wsadata;
if (0 != WSAStartup(MAKEWORD(2, 2), &wsadata))//wsa windows socket ansyc windows异步套接字
{
error(WSAStartup);
return false;
}
return true;
}
bool close_Socket()
{
if (0 != WSACleanup())
{
error(WSACleanup);
return false;
}return true;
}
SOCKET create_serverSocke()
{
//1 创建一个空的socket 地址协议族 流式协议 tcp/udp
SOCKET fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//~0 对于有符号来说式-1 对于无符号来说式最大值
if (INVALID_SOCKET == fd)//对0取反
{
error("socket");
return INVALID_SOCKET;
}
//2 给socket 绑定本地ip地址和端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET;//协议族
addr.sin_port = htons(PORT);//端口号 把本地字节序转为网络字节序,大端存储小端存储
addr.sin_addr.S_un.S_addr = ADDR_ANY;//绑定本地任意ip
if (SOCKET_ERROR == bind(fd, (struct sockaddr*)&addr, sizeof addr))
{
error("bind");
return INVALID_SOCKET;
}
//3 开始监听
listen(fd, 10);
return fd;
}
//客户端
SOCKET create_clientSocket(const char* ip)
{
//1 创建一个空的socket 地址协议族 流式协议 tcp/udp
SOCKET fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//~0 对于有符号来说式-1 对于无符号来说式最大值
if (INVALID_SOCKET == fd)//对0取反
{
error("socket");
return INVALID_SOCKET;
}
//2 给socket 绑定服务端地址和端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET;//协议族
addr.sin_port = htons(PORT);//端口号 把本地字节序转为网络字节序,大端存储小端存储
addr.sin_addr.S_un.S_addr = inet_addr(ip); //绑定服务器ip
if (INVALID_SOCKET == connect(fd, (struct sockaddr*)&addr,sizeof addr))
{
error("connect");
return INVALID_SOCKET;
}
return fd;
}
服务器端:
头文件:TCPSocket.h
#ifndef __TCPSOCKET_H__
#define __TCPSOCKET_H__
#include<iostream>
#include<WinSock2.h>//头文件
#pragma comment (lib,"ws2_32.lib")//库文件
#define error(x) cout << "[error]"<<x <<"failed,line: " << __LINE__ << endl;
#define PORT 8888 //0~1024 是系统保留一般不用
using namespace std;
//初始化网络库
//关闭网络库
//服务器:创建服务器socket
bool init_Socket();
bool close_Socket();
SOCKET create_serverSocke();
//客户端:创建客户端socket
SOCKET create_clientSocket(const char* ip);
#endif // __TCPSOCKET_H__
//服务器的server.cpp:
#include"TcpSocket.h"
int main()
{
init_Socket();
//服务器:创建Socket
SOCKET serfd=create_serverSocke();
cout << "server create successed ,wait client connet..." << endl;
//等待客户端连接
SOCKET clifd=accept(serfd,NULL,NULL);//返回客户端的socket
if (clifd == INVALID_SOCKET)
{
error("accept ");
}
cout << "友好的问候一下" << endl;
//可以和客户端通信
char sendCli[1024] = "已连接服务器";
if (SOCKET_ERROR == send(clifd, sendCli, sizeof sendCli, 0))
{
error("send ");
}
char recvBuf[1024] = "";
while (1)
{
int ret = recv(clifd, recvBuf, 1024, 0);
if (ret == 0)
{
cout << "客户端正常下线..." << endl;
break;
}
else if (ret < 0)
{
error("recv");
break;
}
cout << recvBuf << endl;
}
closesocket(serfd);
close_Socket();
getchar();
}
客户端:
//TCPSocket.h 头文件 与服务器一样
#ifndef __TCPSOCKET_H__
#define __TCPSOCKET_H__
#include<iostream>
#include<WinSock2.h>//头文件
#pragma comment (lib,"ws2_32.lib")//库文件
#define error(x) cout << "[error]"<<x <<"failed,line: " << __LINE__ << endl;
#define PORT 8888 //0~1024 是系统保留一般不用
using namespace std;
//初始化网络库
//关闭网络库
//服务器:创建服务器socket
bool init_Socket();
bool close_Socket();
SOCKET create_serverSocke();
//客户端:创建客户端socket
SOCKET create_clientSocket(const char* ip);
#endif // __TCPSOCKET_H__
//客户端:client.cpp
#include"TcpSocket.h"
int main()
{
init_Socket();
SOCKET fd = create_clientSocket("127.0.0.1");
char recvBuf[1024] = "";
int ret=recv(fd, recvBuf, 1024, 0);
if (ret == 0)
{
cout << "服务器正常下线..." << endl;
}
else if (ret < 0)
{
error("recv");
}
cout << recvBuf << endl;
cout << "请向服务器发送消息,输入end 结束本次发送" << endl;
char sendBuf[1024]="";
while (1)
{
cin >> sendBuf;
if (strcmp(sendBuf, "end") == 0)
break;
send(fd, sendBuf, sizeof (sendBuf), 0);
}
closesocket(fd);
close_Socket();
getchar();
return 0;
}
3.4运行结果:
先运行服务器代码,接着运行客户端代码。
运行服务器后,右击客户端选择调试,启动新实例。
运行成功的样子:
测试输入:
输入"end"客户端关闭: