day01-从一个基础的socket服务说起
首发地址:day01-从一个基础的socket服务说起
教程说明:C++高性能网络服务保姆级教程
本节目的
实现一个基于socket的echo服务端和客户端
服务端监听流程
第一步:使用socket函数创建套接字
在linux中,一切都是文件,所有文件都有一个int类型的编号,称为文件描述符。服务端和客户端通信本质是在各自机器上创建一个文件,称为socket(套接字),然后对该socket文件进行读写。
在 Linux 下使用 <sys/socket.h> 头文件中 socket() 函数来创建套接字
int socket(int af, int type, int protocol);
- af: IP地址类型; IPv4填
AF_INET, IPv6填AF_INET6 - type: 数据传输方式,
SOCK_STREAM表示流格式、面向连接,多用于TCP。SOCK_DGRAM表示数据报格式、无连接,多用于UDP - protocol: 传输协议, IPPROTO_TCP表示TCP。
IPPTOTO_UDP表示UDP。可直接填0,会自动根据前面的两个参数自动推导协议类型
#include <sys/socket.h>
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
第二步:使用bind函数绑定套接字和监听地址
socket()函数创建出套接字后,套接字中并没有任何地址信息。需要用bind()函数将套接字和监听的IP和端口绑定起来,这样当有数据到该IP和端口时,系统才知道需要交给绑定的套接字处理。
bind函数也在<sys/socket.h>头文件中,原型为:
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
- sock: socket函数返回的socket描述符
- addr:一个sockaddr结构体变量的指针,后续会展开说。
- addrlen:addr的大小,直接通过sizeof得到
我们先看看socket和bind的绑定代码,下面代码中,我们将创建的socket与ip='127.0.0.1',port=8888进行绑定:
#include <sys/socket.h>
#include <netinet/in.h>
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr)); //用0填充
server_addr.sin_family = AF_INET; //使用IPv4地址
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址;填入INADDR_ANY表示"0.0.0.0"
server_addr.sin_port = htons(8888); //端口
//将套接字和IP、端口绑定
bind(server_addr, (struct sockaddr*)&server_addr, sizeof(server_addr));
可以看到,我们使用sockaddr_in结构体设置要绑定的地址信息,然后再强制转换为sockaddr类型。这是为了让bind函数能适应多种协议。
struct sockaddr_in{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
uint16_t sin_port; //16位的端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用,一般用0填充
};
struct sockaddr_in6 {
sa_family_t sin6_family; //(2)地址类型,取值为AF_INET6
in_port_t sin6_port; //(2)16位端口号
uint32_t sin6_flowinfo; //(4)IPv6流信息
struct in6_addr sin6_addr; //(4)具体的IPv6地址
uint32_t sin6_scope_id; //(4)接口范围ID
};
struct sockaddr{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
char sa_data[14]; //IP地址和端口号
};
其中,sockaddr_in是保存IPv4的结构体;sockadd_in6是保存IPv6的结构体;sockaddr是通用的结构体,通过将特定协议的结构体转换成sockaddr,以达到bind可绑定多种协议的目的。
注意在设置server_addr的端口号时,需要使用htons函数将传进来的端口号转换成大端字节序
计算机硬件有两种储存数值的方式:大端字节序和小端字节序
大端字节序指数值的高位字节存在前面(低内存地址),低位字节存在后面(高内存地址)。
小端字节序则反过来,低位字节存在前面,高位字节存在后面。
计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。而计算机读内存数据都是从低地址往高地址读。所以,计算机的内部是小端字节序。但是,人类还是习惯读写大端字节序。除了计算机的内部处理,其他的场合比如网络传输和文件储存,几乎都是用的大端字节序。
linux在头文件<arpa/inet.h>提供了htonl/htons用于将数值转化为网络传输使用的大端字节序储存;对应的有ntohl/ntohs用于将数值从网络传输使用的大端字节序转化为计算机使用的字节序
第三步:使用listen函数让套接字进入监听状态
int listen(int sock, int backlog); //Linux
- backlog:表示全连接队列的大小
半连接队列&全连接队列:我们都知道tcp的三次握手,在第一次握手时,服务端收到客户端的SYN后,会把这个连接放入半连接队列中。然后发送ACK+SYN。在收到客户端的ACK回包后,握手完成,会把连接从半连接队列移到全连接队列中,等待处理。
第四步:调用accept函数获取客户端请求
调用listen后,此时客户端就可以和服务端三次握手建立连接了,但建立的连接会被放到全连接队列中。accept就是从这个队列中获取客户端请求。每调用一次accept,会从队列中获取一个客户端请求。
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
- sock:服务端监听的socket
- addr:获取到的客户端地址信息
accpet返回一个新的套接字,之后服务端用这个套接字与连接对应的客户端进行通信。
在没请求进来时调用accept会阻塞程序,直到新的请求进来。
至此,我们就讲完了服务端的监听流程,接下来我们可以先调用read等待读入客户端发过来的数据,然后再调用write向客户端发送数据。再用close把accept_fd关闭,断开连接。完整代码如下
// server.cpp
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <cstdio>
#include <errno.h>
int main() {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(8888);
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
printf("bind err: %s\n", strerror(errno));
close(listen_fd);
return -1;
}
if (listen(listen_fd, 2048) < 0) {
printf("listen err: %s\n", strerror(errno));
close(listen_fd);
return -1;
}
struct sockaddr_in client_addr;
bzero(&client_addr, sizeof(struct sockaddr_in));
socklen_t client_addr_len = sizeof(client_addr);
int accept_fd = 0;
while((accept_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_addr_len)) > 0) {
printf("get accept_fd: %d from: %s:%d\n", accept_fd, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
char read_msg[100];
int read_num = read(accept_fd, read_msg, 100);
printf("get msg from client: %s\n", read_msg);
int write_num = write(accept_fd, read_msg, read_num);
close(accept_fd);
}
}
[C++小知识] 在使用printf打印调试信息时,由于系统缓冲区问题,如果不加"\n",有时会打印不出来字符串。
C提供的很多函数调用产生错误时,会将错误码赋值到一个全局int变量errno上,可以通过strerror(errno)输入具体的报错信息
客户端建立连接
客户端就比较简单了,创建一个sockaddr_in变量,填充服务端的ip和端口,通过connect调用就可以获取到一个与服务端通信的套接字。
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
各个参数的说明和bind()相同,不再重复。
创建连接后,我们先调write向服务端发送数据,再调用read等待读入服务端发过来的数据,然后调用close断开连接。完整代码如下:
// client.cpp
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <cstdio>
#include <iostream>
int main() {
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(8888);
if (connect(sock_fd, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
printf("connect err: %s\n", strerror(errno));
return -1;
};
printf("success connect to server\n");
char input_msg[100];
// 等待输入数据
std::cin >> input_msg;
printf("input_msg: %s\n", input_msg);
int write_num = write(sock_fd, input_msg, 100);
char read_msg[100];
int read_num = read(sock_fd, read_msg, 100);
printf("get from server: %s\n", read_msg);
close(sock_fd);
}
分别编译后,我们就得到了一个echo服务的服务端和客户端
~# ./server
get accept_fd: 4 from: 127.0.0.1:56716
get msg from client: abc
~# ./client
abc
input_msg: abc
get from server: abc
完整源码已上传到CProxy-tutorial,欢迎fork and star!
思考题
先启动server,然后启动一个client,不输入数据,这个时候在另外一个终端上再启动一个client,并在第二个client终端中输入数据,会发生什么呢?
如果本文对你有用,点个赞再走吧!或者关注我,我会带来更多优质的内容。
day01-从一个基础的socket服务说起的更多相关文章
- 用一个下午从零开始搭建一个基础lbs查询服务
背景 现在做一个sns如果没有附近的功能,那就是残缺的.网上也有很多现成的lbs服务,封装的很完整了. 我首先用了下百度lbs云,但是有点不适合自己的需要,因此考虑用mongodb建一个简单的lbs服 ...
- 使用NewLife网络库构建可靠的自动售货机Socket服务端(一)
最近有个基于tcp socket 协议和设备交互需求,想到了新生命团队的各种组件,所以决定用NewLife网络库作为服务端来完成一系列的信息交互. 第一,首先说一下我们需要实现的功能需求吧 1,首先客 ...
- Socket探索1-两种Socket服务端实现
介绍 一次简单的Socket探索之旅,分别对Socket服务端的两种方式进行了测试和解析. CommonSocket 代码实现 实现一个简单的Socket服务,基本功能就是接收消息然后加上结束消息时间 ...
- Python进阶----SOCKET套接字基础, 客户端与服务端通信, 执行远端命令.
Python进阶----SOCKET套接字基础, 客户端与服务端通信, 执行远端命令. 一丶socket套接字 什么是socket套接字: 专业理解: socket是应用层与TCP/IP ...
- 第一个socket服务端程序
第一个socket服务端程序 #include <stdio.h> #include <stdlib.h> #include <string.h> #include ...
- 使用PHP创建一个socket服务端
与常规web开发不同,使用socket开发可以摆脱http的限制.可自定义协议,使用长连接.PHP代码常驻内存等.学习资料来源于workerman官方视频与文档. 通常创建一个socket服务包括这几 ...
- apache服务器本质上说是一个TCP socket服务
apache服务器本质上说是一个TCP socket服务,socket模型如下: 下面以worker MPM来说明apache代码中相应处理的位置在哪里: (以apache httpd 2.2.23 ...
- 今天介绍一下自己的开源项目,一款以spring cloud alibaba为核心的微服务架构项目,为给企业与个人提供一个零开发基础的微服务架构。
LaoCat-Spring-Cloud-Scaffold 一款以spring cloud alibab 为核心的微服务框架,主要目标为了提升自己的相关技术,也为了给企业与个人提供一个零开发基础的微服务 ...
- Python网络编程基础 ❸ struct模块 基于upd的socket服务
struct模块 基于upd的socket服务
随机推荐
- JavaScript01 js基础语法,数据类型
JavaScript的概述: 1.组成 三部分组成 ecmaScript 基础语法 (es5) dom document object model 文档对象模型 (操作html文档内容) bom bo ...
- Redis+Caffeine两级缓存,让访问速度纵享丝滑
原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 在高性能的服务架构设计中,缓存是一个不可或缺的环节.在实际的项目中,我们通常会将一些热点数据存储到Redis或MemCache这类缓存中间件中, ...
- 那么回到我们开始的问题,通常一棵B+树可以存放多少行数据?
这里我们先假设B+树高为2,即存在一个根节点和若干个叶子节点,那么这棵B+树的存放总记录数为:根节点指针数*单个叶子节点记录行数. 上文我们已经说明单个叶子节点(页)中的记录数=16K/1K=16.( ...
- 在Spring AOP 中,关注点和横切关注的区别是什么?
关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能. 横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块 ...
- kafka unclean 配置代表啥,会对 spark streaming 消费有什么影响?
unclean.leader.election.enable 为true的话,意味着非ISR集合的broker 也可以参与选举,这样有可能就会丢数据,spark streaming在消费过程中拿到的 ...
- screen--后台不挂断运行
方法一:1.进入项目目录下,运行下面程序:nohup python manage.py runserver 0.0.0.0:5008 &nohup(no hang up)用途:不挂断的运行命令 ...
- C++11最常用的新特性如下
1.auto关键字:编译器可以根据初始值自动推导出类型.但是不能用于函数传参.定义数组以及非静态成员变量. 2.nullptr关键字:是一种特殊类型的字面值,它可以被转换成任意其它类型的指针:而NUL ...
- Django中间件 (middleware)
中间件是处理django的请求和响应的框架级别的钩子,本质是一个类(直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作) 由于其影响的是全局,所以需要谨慎使用,使用不当会影响性 ...
- centos 后台 运行jar包方法
后台运行jar包方法: 1.nohup java -jar shareniu.jar & nohup意为后台不挂断运行,与是否账号退出无关 2.nohup java -jar shareniu ...
- Python学习—(windows系统下)安装pygame
浏览器搜索pygame的windows安装程序,下载与python版本相匹配的文件. 如果.exe文件直接运行: 如果.whl文件,将其复制到要用到的项目文件夹中,在cmd窗口中切换到该文件所在的文件 ...