Socket编程模型

news/2024/7/7 21:23:26

一、整体过程图解

                 

二、Socket编程模型细节

        客户端和服务器能在网络中通信,那必须得使用Socket编程,它是进程间通信里比较特别的方式,特别之处在于它是可以跨主机间通信。
创建Socket 的时候,可以指定网络层使用的是IPv4还是IPv6,传输层使用的是TCP还是UDP。服务器的程序要先跑起来,然后等待客户端的连接和数据,我们先来看看服务端的Socket编程过程是怎样的。
服务端首先调用socket()函数,创建网络协议为IPv4,以及传输协议为TCP的Socket。

接着调用bind()函数,给这个Socket绑定一个IP地址和端口,绑定这两个的目的是什么?

  • 绑定端口的目的:当内核收到TCP报文,通过TCP头里面的端口号,来找到我们的应用程序,然后把数据传递给我们。
  • 绑定IP地址的目的:一台机器是可以有多个网卡的,每个网卡都有对应的IP地址,当绑定一个网卡时,内核在收到该网卡上的包,才会发给我们;

绑定完IP地址和端口后,就可以调用listen()函数进行监听,此时对应TCP状态图中的listen,如果我们要判定服务器中一个网络程序有没有启动,可以通过netstat命令查看对应的端口号是否有被监听。


服务端进入了监听状态后,通过调用accept()函数,来从内核获取客户端的连接,如果没有客户端连接,则会阻塞等待客户端连接的到来。


那客户端是怎么发起连接的呢?客户端在创建好Socket后,调用connect()函数发起连接,该函数的参数要指明服务端的IP地址和端口号,然后万众期待的TCP三次握手就开始了。
在TCP连接的过程中,服务器的内核实际上为每个Socket维护了两个队列:

  • 一个是「还没完全建立」连接的队列,称为TCP半连接队列,这个队列都是没有完成三次握手的连接,此时服务端处于syn_rcvd 的状态;
  • 一个是「已经建立」连接的队列,称为TCP全连接队列,这个队列都是完成了三次握手的连接,此时服务端处于established 状态;

当TCP全连接队列不为空后,服务端的accept()函数,就会从内核中的TCP全连接队列里拿出一个已经完成连接的Socket返回应用程序,后续数据传输都用这个Socket。
注意,监听的Socket和真正用来传数据的Socket是两个:

  • 一个叫作监听Socket;
  • 一个叫作已连接Socket;

连接建立后,客户端和服务端就开始相互传输数据了,双方都可以通过read()和write()函数来读写数据。

三、服务端、客户端代码供参考

服务器代码:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<netinet/ip.h>
#include<arpa/inet.h> 

#define SERROPT 8000
#define SERIP "192.168.111.110"
int main(int argc, char* argv[])                                                                                                                                               
{
     //创建一个套接字 socket参数1.协议类型 2.流式套接字 3.传0(默认TCP协议)
     int lfd = socket(AF_INET, SOCK_STREAM, 0);
     struct sockaddr_in seraddr, cliaddr;
     seraddr.sin_family = AF_INET;
     seraddr.sin_port = htons(SERROPT);       //8000->16415
     seraddr.sin_addr.s_addr=INADDR_ANY;
     //seraddr.sin_addr.s_addr = 2171971776;  //192.168.117.129->2171971776
     //inet_pton(AF_INET, SERIP ,(void*)&seraddr.sin_addr.s_addr);//本地ip转网络ip函数
     //参数1.要绑定文件描述符 2.结构体包括(协议类型、端口号、地址)3.长度
     bind (lfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
     listen(lfd,64);
     //
     socklen_t clilen = sizeof(cliaddr);
     int cfd = accept(lfd , (struct sockaddr*)&cliaddr, &clilen);//第三个套接字 
     //accept是在全连接队列中取出连接 如果多次连接 全连接里没有连接可用 会发生阻塞
     char buf[1024];
     while(1)
     {
        int rr = read(cfd, buf, sizeof(buf));//读 读缓冲
        write(STDERR_FILENO, buf, rr);   //写到标准输出上
        write(cfd, buf, rr); //反射回去证明客户端已经接收到了
     }
     return 0;      
} 
   

客户端代码:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<netinet/ip.h>
#include<arpa/inet.h> 

#define SERROPT 8000
#define SERIP "192.168.111.110"
#define CLIPORT 8001
#define CLIIP "192.168.111.110" 
int main(int argc, char* argv[])                                                                                                                                               
{
     //创建一个套接字 
     int cfd = socket(AF_INET, SOCK_STREAM, 0);
     struct sockaddr_in seraddr, cliaddr;   //定义两个结构体

     cliaddr.sin_family = AF_INET;
     cliaddr.sin_port = htons(CLIPORT);      
     inet_pton(AF_INET, CLIIP ,(void*)&cliaddr.sin_addr.s_addr);
     bind (cfd, (struct sockaddr*)&cliaddr, sizeof(cliaddr));  //客户端绑定
     //
     seraddr.sin_family = AF_INET;
     seraddr.sin_port = htons(SERROPT);       
     inet_pton(AF_INET, SERIP ,(void*)&seraddr.sin_addr.s_addr);
     connect(cfd, (struct sockaddr*)&seraddr, sizeof(seraddr));//连接
         
     char buf[1024];
     while(1)
     {
        int rr = read(0, buf, sizeof(buf)); //等待输入
        write(cfd, buf, rr);  
        rr = read(cfd, buf, sizeof(buf));
        write(1, buf, rr); //反射回去证明已经接收到了
     }
     return 0;      
} 
   

        端口随便设定合理就行,IP地址(我这里随便写的IP)查看自己虚拟机IP(通过命令:ifconfig)查看,不要忘记更改,先启动服务器、再启动客户端,这样一个简单的Socket编程模型就搭建好了。

        基于Linux一切皆文件的理念,在内核中Socket也是以「文件」的形式存在的,也是有对应的文件描述符。


http://lihuaxi.xjx100.cn/news/2220541.html

相关文章

从0到1,揭秘AI产品经理的高薪秘诀,转型之路与实战资源全解析

前言 随着算法模型的日益精进、计算能力的显著提升以及海量数据的积累&#xff0c;人工智能领域正以前所未有的速度蓬勃发展。 在国家政策的积极推动、社会资本的强劲注入下&#xff0c;人工智能产业正处于技术快速进步的黄金时期&#xff0c;其影响力广泛渗透至教育智能化、…

异步爬虫:aiohttp 异步请求库使用:

使用requests 请求库虽然可以完成爬虫业务&#xff0c;但是对于异步任务来说&#xff0c;它是做不到的&#xff0c; 这时候我们需要借助 aiohttp 异步请求库来完成异步爬虫的编写&#xff1a; 话不多说&#xff0c;直接看示例&#xff1a; 注意&#xff1a;楼主使用的python版…

【Spine学习12】之 事件帧

1、新建事件帧&#xff1a; 2、选择第8s的攻击帧&#xff0c;点击第一步新建的attack事件帧前面的钥匙 这样每次动作到8s的时候会自动跳出事件帧提示 这个文字实际动画不会显示 事件是动画过程中所发生情况的触发器。 给程序员识别的

MySQL数据库回顾(1)

数据库相关概念 关系型数据库 概念: 建立在关系模型基础上&#xff0c;由多张相互连接的二维表组成的数据库。 特点&#xff1a; 1.使用表存储数据&#xff0c;格式统一&#xff0c;便于维护 2.使用SQL语言操作&#xff0c;标准统一&#xff0c;使用方便 SOL SQL通用语法 …

python学习笔记-07

python内置函数 内置函数就是python自带的函数&#xff0c;不需要我们再去定义的&#xff0c;如print等直接使用即可&#xff0c;内置函数官方文档&#xff1a;官链。 1.数学运算 #数学运算&#xff1a; print(------abs()是绝对值函数------) a-1.1 print({}的绝对值是{}.fo…

Git与SSH

Git Git是一种分布式版本控制系统&#xff0c;最初由Linus Torvalds为管理Linux内核开发而设计并开发。Git可以帮助开发团队协作管理代码&#xff0c;跟踪代码变更历史&#xff0c;并在需要时回溯到特定版本。 分布式版本控制&#xff1a;每个开发者都可以拥有完整的代码仓库…

python+unity手势控制地球大小

效果图如下 具体操作如下 1 在unity窗口添加一个球体 2 给球体添加材质,材质图片使用地球图片 地球图片如下 unity材质设置截图如下 3 编写地球控制脚本 using System.Collections; using System.Collections.Generic; using UnityEngine;public class test : MonoBehavio…

【JavaScript脚本宇宙】玩转图像处理:从基础到高级,这些库你不能错过!

让你的网页图像栩栩如生&#xff1a;六种必备图像处理库 前言 在数字图像处理中&#xff0c;我们经常需要对图片进行各种操作&#xff0c;如调整亮度、对比度、饱和度等&#xff0c;以达到所需的效果。为了简化这些操作并提供更丰富的功能&#xff0c;出现了许多专门用于图像…