Socket通信原理和QQ的基本实现

前言

最近在看Socket网络编程这一部分,本来以为跟教程下来就算了解了,但是一个腾讯学长问了一个基础问题,竟然都答不上来,有必要去了解一些基础知识了,各函数之间的作用,之间的联系,我浏览了很多大佬的文章,进行总结,但是有些问题还没有解决,接下来就针对问题进行总结。末尾有惊喜,不进行一一介绍了。懂得都懂。

一、网络中进程之间通信方式

消息传递(管道、FIFO、消息队列)
同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
共享内存(匿名的和具名的)
远程过程调用(Solaris门和Sun RPC)

标识进程才能进行通信,IP地址就可以标识网络中的主机,传输层的协议+端口就是唯一标识主机中的应用程序。IP地址+端口就可以标识网络端口,在Socket中进程通信可以利用这个标志与其它进程交互。

二、Socket的基本使用

socket是“open—write/read—close”模式的一种实现,那么socket就提供了这些操作对应的函数接口。

1.Socket函数

socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

创建socket的时候,也可以指定不同的参数创建不同的socket描述符。

<1>domain:即协议域,又称为协议族(family).常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE.协议族决定了socket的地址类型,在通信中必须采用对应的地址

<2>type:指定socket类型。常用的socket类型,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等

<3>protocol:指定协议。常用的协议:IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议.

当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

2.bind()函数

bind()函数把一个地址族中的特定地址赋给socket,AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

<1>sockfd:socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。

<2>addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。

<3>addrlen:对应的是地址的长度。

通常服务器在启动的时候都会绑定一个众所周知的地址(ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

3.listen()、connect()函数

如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

<1> listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

<2>connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

4.accept()函数

TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了,之后就可以进行I/O了。

ccept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

5.read()、write()、close()函数

服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信,推荐使用recvmsg()/sendmsg()函数,这两个函数是最通用的I/O函数。

完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。

注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

三、QQ的基本实现

C语言版本

客户端:

#include <iostream>
#include <stdio.h>
//#include <windows.h>
#include<graphics.h>//easyX
#include <stdlib.h>
#pragma comment(lib,"ws2_32.lib")

SOCKET clientSocket;
HWND hWnd;
int count = 0;

void JS()
{
    
    
	char recvBuff[1024];
	int r;
	while (1)
	{
    
    
		r = recv(clientSocket, recvBuff, 1023, NULL);
		if (r>0)
		{
    
    
			recvBuff[r] = 0;

			outtextxy(0, count * 20, recvBuff);
			count++;
		}
	}
}



int main()
{
    
    
	//初始化界面
	hWnd=initgraph(300, 400, SHOWCONSOLE);
	//1.请求协议版本
	WSADATA WSAData;
	WSAStartup(MAKEWORD(2, 2), &WSAData);//2.2协议版本用的比较多,可以修改
	if (LOBYTE(WSAData.wVersion) != 2 || HIBYTE(WSAData.wVersion) != 2)
	{
    
    
		printf("请求协议版本失败!\n");//一般不会出错
		return -1;
	}
	printf("请求协议版本成功!\n");
	//2.创建Socket
	SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (SOCKET_ERROR == clientSocket)
	{
    
    
		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("10.0.117.22");//用自己的IP
	addr.sin_port = htons(10086);//大端转小端,一般电脑具备0-65535个端口   建议使用10000左右的,os内核和其他程序会占用一些端口


	//4.连接服务器
	int r = connect(clientSocket, (sockaddr*)&addr, sizeof addr);
	if (r == -1)
	{
    
    
		printf("连接服务器失败!\n");
		return -1;
	}
	printf("连接服务器成功!\n");

	//5.通信 
	char buff[1024] ;
	CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)JS, NULL, NULL, NULL);
	while (1)
	{
    
    
		memset(buff, 0, 1024);
		printf("你想说啥:");
		scanf_s("%s", buff);
		send(clientSocket, buff, strlen(buff), NULL);
		//send(clientSocket, buff, 1023, NULL);
	}
	system("pause");
    return 0;


}

服务端:

#include <iostream>
#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 tongxin(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);//2.2协议版本用的比较多,可以修改
    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("10.0.117.22");//用自己的IP
    addr.sin_port = htons(10086);//大端转小端,一般电脑具备0-65535个端口   建议使用10000左右的,os内核和其他程序会占用一些端口

    //4.绑定地址
    int r = bind(serverSocket, (sockaddr*)&addr, sizeof addr);
    if (-1 == r)
    {
    
    
        printf("bind 失败!\n");
        closesocket(serverSocket);
        WSACleanup();
        return -2;
    }
    printf("bind 成功!\n");

    //5.监听
    r = listen(serverSocket, 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");
            //关闭Socket
            closesocket(serverSocket);
            //清除协议信息
            WSACleanup();
            return -2;
        }
        printf("有客户端连接到服务器:%s\n!\n", inet_ntoa(cAddr.sin_addr));

        CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)tongxin, (char*)count, NULL, NULL);

        count++;
    }

}

C++版本

客户端:

#include <stdlib.h>
#include <stdio.h>
#include <WinSock2.h>
#include <iostream>
#include <cstringt.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
const int BUF_SIZE = 2048;//设置一个缓冲区
SOCKET sockSer, sockCli;
SOCKADDR_IN addrSer, addrcli;//地址
int naddr = sizeof(SOCKADDR_IN);

char sendbuf[BUF_SIZE];//发送的缓冲区
char inputbuf[BUF_SIZE];//输入内容的缓冲区
char recvbuf[BUF_SIZE];//接收信息的缓冲区
int main()
{
    
    

	//加载Socket库
	WSADATA wsadata;
	if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
	{
    
    
		//输出出错信息
		cout << "载入socket库失败" << endl;
		system("pause");
		return 0;
	}

	//创建Socket
	sockCli = socket(AF_INET, SOCK_STREAM, 0);//ipv4
	//初始化客户端地址包ipv4
	addrcli.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//不进行任何传输,直接把本机地址返回
	addrcli.sin_family = AF_INET;
	addrcli.sin_port = htons(10086);

	//初始化服务器地址
	addrSer.sin_family = AF_INET;
	addrSer.sin_addr.S_un.S_addr = inet_addr("10.0.117.43");
	addrSer.sin_port = htons(10086);

	while (1)
	{
    
    
		if (connect(sockCli, (SOCKADDR*)&addrSer, sizeof(addrSer)) != SOCKET_ERROR)
		{
    
    
			recv(sockCli, recvbuf, sizeof(sendbuf), 0);
			cout << recvbuf << endl;
		}
	}
	closesocket(sockCli);
	closesocket(sockSer);

	WSACleanup();
	return 0;
}```
**服务端:**

```cpp
#include <stdlib.h>
#include <stdio.h>
#include <WinSock2.h>//WindowsSocket编程头文件
#include <iostream>
#include<cstringt.h>
#pragma comment(lib,"ws2_32.lib")//链接ws2_32.lib库文件到此项目

using namespace std;
const int BUF_SIZE = 2048;//设置一个缓冲区
SOCKET sockSer, sockCli;
SOCKADDR_IN addrSer, addrcli;//地址
int naddr = sizeof(SOCKADDR_IN);

char sendbuf[BUF_SIZE];//发送的缓冲区
char inputbuf[BUF_SIZE];//输入内容的缓冲区
char recvbuf[BUF_SIZE];//接收信息的缓冲区
int main()
{
    
    
    //加载Socket库
    WSADATA wsadata;
    if (WSAStartup(MAKEWORD(2,2),&wsadata)!=0)
    {
    
    
        //输出出错信息
        cout << "载入socket库失败" << endl;
        system("pause");
        return 0;
    }
    
    //创建Socket
    sockSer = socket(AF_INET, SOCK_STREAM, 0);//ipv4
    //初始化地址包ipv4
    addrSer.sin_addr.S_un.S_addr = inet_addr("10.0.117.43");
    addrSer.sin_family = AF_INET;
    addrSer.sin_port = htons(10086);

    //绑定Socket
    bind(sockSer, (SOCKADDR*)&addrSer, sizeof(SOCKADDR));
    while (1)
    {
    
    
        //监听连接请求
        listen(sockSer, 5);
        //接收连接请求
        sockCli=accept(sockSer, (SOCKADDR*)&addrcli, &naddr);
        if (sockCli!=INVALID_SOCKET)//连接成功
        {
    
    
            cout << "连接成功!" << endl;
            strcpy(sendbuf, "hello!");

            send(sockCli, sendbuf, sizeof(sendbuf), 0);
        }
    }
	closesocket(sockCli);
	closesocket(sockSer);

	WSACleanup();
    return 0;

}```



猜你喜欢

转载自blog.csdn.net/qq_52269550/article/details/117377994
今日推荐