一、理解socket
-
套接字
应用进程跨网络的通信需要用到socket套接字实现,在TCP/IP协议中,“IP地址+TCP或UDP“唯一的标识了网络通信中的一个进程,这也就叫做一个套接字。 -
套接字产生
引入套接字就不得不介绍一下**系统调用**和**应用编程接口**。 当我们有一些特定的应用需要互联网的支持,但这些应用又不能直接使用已经标准化的互联网协议,那么我们应当做哪些工作? 大多数操作使用**系统调用**(system call)的机制在应用程序和操作系统之间传递控制权。
当某个应用进程启动系统调用时,控制权就从应用进程传递给系统调用接口。此接口再把控制权传递给操作系统,操作系统把这个调用转给某个内部过程,并执行所请求的操作。内部操作一旦执行完毕,控制权就又通过系统调用接口返回给应用进程。因此,系统调用接口实际上就是应用进程的控制权和操作系统的控制权进行转换的一个接口。由于应用进程在使用系统调用接口之前要编写一些程序,特别是要设置系统调用中的许多参数,因此这种系统调用接口又称为应用编程接口API(Application Programming Interface) 。现在的TCP/IP协议软件已经驻留在操作系统中了。由于TCP/IP协议族被设计成能够运行在多种操作系统的环境中,因此TCP/IP标准没有规定应用程序与TCP/IP协议软件如何接口的细节,而是允许系统设计者能够选择有关API的具体实现细节。目前只有几种可供应用程序使用TCP/IP的应用编程接口API,其中最著名的就是美国加利福尼亚大学伯克利分校为Berkeley UNIX操作系统定义的一种API,它又称为套接字接口(socket interface)。微软在其操作系统中采用了套接字接口API,形成了一个稍有不同的API,并称之为Windows Socket,简称WinSock。(小编在这里只用了微软的)
1.
3.调用socket创建套接字
- 以使用TCP服务为例的系统调用
4.1.建立连接阶段
当套接字被创建后,它的端口号和地址都是空的,进程需要调用Bind来指明套接字本地地址(本地端口号和IP地址)。在服务器端Bind就是把熟知端口号本地IP地址填写到自己创建的套接字里。
服务器调用Bind后还必须调用Listen把套接字设置为被动模式,随时接受客户端服务请求。
服务器紧接着调用accept,把远程客户端发送的连接请求提取出来。此处可以并发,因为处理了多个连接请求。
4.2.数据传送阶段
客户和服务器都在TCP连接上使用send系统调用传送数据,使用recv系统调用接收数据。而各函数的定义可在编译器中具体查看。
4.3.连接释放
客户或服务器结束使用套接字,就把套接字撤销。调用close。
二、具体实现
实现环境:vs2019,c语言,
//server.c端完整代码
#include<stdio.h>
#include<windows.h>
#pragma comment(lib,"ws2_32.lib")
SOCKADDR_IN cAddr = {
0 };
int len = sizeof cAddr;
SOCKET clientSocket[1024];
int count = 0;
void tonxin(int idx)
{
char buff[1024];
int r;
while (1)
{
r = recv(clientSocket[idx], buff, 1023, NULL);
if (r > 0)
{
buff[r] = 0;
printf("%d:%s\n", idx, buff);
//广播数据
for (int i = 0; i < count; i++)
{
send(clientSocket[i], buff, strlen(buff), NULL);
}
}
}
}
int main()
{
//1请求协议版本
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("请求协议版本失败!\n");
return -1;
}
printf("请求协议成功!\n");
//2创建socket
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (SOCKET_ERROR == serverSocket)
{
printf("创建socket失败!\n");
WSACleanup();
return -2;
}
printf("创建socket成功!\n");
//3创建协议地址族
SOCKADDR_IN addr = {
0 };
addr.sin_family = AF_INET;//协议族
addr.sin_addr.S_un.S_addr = inet_addr("");//ip地址
addr.sin_port = htons(5000);//0-65535端口
//4绑定
int r = bind(serverSocket, (SOCKADDR*)&addr, sizeof addr);
if (-1 == r)
{
printf("bind失败!\n");
closesocket(serverSocket);
WSACleanup();
return -3;
}
printf("bind成功!\n");
//5监听
r = listen(serverSocket, 10);//同时最多监听10台
if (-1 == r)
{
printf("listen失败!\n");
closesocket(serverSocket);
WSACleanup();
return -2;
}
printf("listen成功!\n");
//6等待客户端连接
while (1)
{
clientSocket[count] = accept(serverSocket, (SOCKADDR*)&cAddr, &len);
if (SOCKET_ERROR == clientSocket[count])
{
printf("服务器宕机!\n");
//8关闭socket
closesocket(serverSocket);
//9清除协议
WSACleanup();
return -2;
}
printf("有客户端连接到服务器了:%s!\n", inet_ntoa(cAddr.sin_addr));//ip地址从整型转换成二进制
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)tonxin, (char*)count, NULL, NULL);
count++;
}
//7通信
char buff[1024];
while (1)
{
r = recv(clientSocket[count], buff, 1023, NULL);
if (r > 0)
{
buff[r] = 0;//添加个结束符号\0
printf(">>%s\n", buff);
}
}
return 0;
}
- 客户端cilent.c
#include<stdio.h>
#include<windows.h>
#include<graphics.h>
#pragma comment(lib,"ws2_32.lib")//包含socket套接字
SOCKET cilentSocket;
HWND hWnd;//在屏幕上显示
int count = 0;
void receive()
{
char recvBuff[1024];
int r;
while (1)
{
r = recv(cilentSocket, recvBuff, 1023, NULL);
if (r > 0)
{
recvBuff[r] = 0;
outtextxy(0, count * 20, recvBuff);//x,y为坐标
count++;
}
}
}
int main()
{
//初始化界面
hWnd = initgraph(300, 300, SHOWCONSOLE);//init()初始化函数
//1请求协议版本
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("请求协议版本失败\n");
return -1;
}
printf("请求协议版本成功\n");
//2创建socket对象
cilentSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (cilentSocket == SOCKET_ERROR)
{
printf("创建socket失败\n");
WSACleanup();
return -1;
}
printf("创建socket成功\n");
//3获取协议地址族
SOCKADDR_IN addr = {
0 };//初始化
addr.sin_family = AF_INET;//协议族
addr.sin_addr.S_un.S_addr = inet_addr("");//ip地址
addr.sin_port = htons(5000);//端口号0-65535
//4连接服务器
int r = connect(cilentSocket, (SOCKADDR*)&addr, sizeof addr);
if (r == -1)
{
printf("连接服务器失败\n");
return -1;
}
printf("连接服务器成功\n");
//5通信
char buff[1024];
//创建线程
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)receive, NULL, NULL, NULL);
while (1)
{
memset(buff, 0, 1024);//防止出问题,每次发送前清空一下
printf("你想说什么?\n");
scanf_s("%s", buff);
r = send(cilentSocket, buff, strlen(buff), NULL);
}
return 0;
}
cilent端的代码在vs2019中有个错误会报出来,不过不用担心,这个错误不是代码错误,而是环境问题,就是在vs2003以后的版本字符集默认是Unicode,只需要在”项目“----”属性“----”高级“----”字符集(Unicode)“改为----”多字节字符集“
即可通过。
运行结果截图类似于如下:
图中表明服务器端确实连到了其它客户端,该图中左边的IP地址填入的应为服务器端IP(小编填的是自己的电脑连的宽度网);而客户端(最好找台另外的电脑,比如说室友的,让他的电脑在vs2019运行cilent.c,你就会很明显的感受到初学时连到情况了。)的IP是客户端自己本机的IP地址。
以上便是小编第一次成功实现socket的情况,初学较浅,望君善待之。