TCP交互流程:

服务器:1. 创建socket;2. 绑定socket和端口号;3. 监听端口号;4. 接收来自客户端的连接请求;5. 从socket中读取字符;6. 关闭socket。

客户端:1. 创建socket;2. 连接指定计算机的端口;3. 向socket中写入信息;4. 关闭socket。

创建socket:

socket函数

int socket (int __family, int __type, int __protocol);

__family是协议域,也称协议族。常见的有AF_INET(ipv4)。

__type指定socket类型。SOCK_STREAM即TCP协议,SOCK_DGRAM即UDP协议。

__protocol指定协议。

该函数返回的socket描述字存在于协议族空间中,但是并没有一个具体的地址。如果想要给它赋予一个地址,就必须调用bind()函数,否则系统就在调用connect()和listen()时自动随机分配一个端口。

这里注意:type和protocol并不能随意组合。当protocol为0时,会自动选择type类型对应的默认协议。

创建socket的样例代码如下:

    //创建TCP套接字
//AF_INET:网络连接,ipv4
//SOCK_STREAM:TCP连接
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd<0) {
std::cout<<"create socket error!"<<std::endl;
return 0;
}
std::cout<<"create socket: "<<fd<<std::endl;

绑定socket和端口号:

bind函数

int bind (int, const struct sockaddr *__my_addr, socklen_t __addrlen);

第一个参数是socket描述字。(我不理解为啥这儿没有参数名)

__my_addr是指向要绑定给该socket的协议地址。这个地址结构根据socket创建时的地址协议族(family)的不同而不同。

__addrlen对应的是地址的长度。

如果该函数执行成功,就返回0,否则为SOCKET_ERROR。

    //命名套接字
struct sockaddr_in myaddr;
memset((void *)&myaddr, 0, sizeof(myaddr));
//关于htonl和htons,参考以下网页:ntohs, ntohl, htons,htonl的比较和详解
//https://blog.csdn.net/haoxiaodao/article/details/73162663
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
myaddr.sin_port = htons(6666);
if (bind(fd, (struct sockaddr*)&myaddr, sizeof(myaddr)) < 0) {
std::cout<<"name socket error!"<<std::endl;
return 0;
}
std::cout<<"name socket"<<std::endl;

监听端口号:

作为一个服务器,在调用socket()和bind()之后就会调用listen()来监听这个socket。为了能够在套接字上接受进入的连接,服务器程序必须创建一个队列来保存未处理的请求。

listen函数

int listen (int, int __n);

第一个参数即socket描述子

__n为队列的大小。

    //创建监听队列
if (listen(fd, 5) < 0) {
std::cout<<"listen failed"<<std::endl;
return 0;
}

接收来自客户端的连接请求:

当TCP服务器监听到了连接请求之后,就会调用accept()函数接收请求,这样连接就建立好了。

accept函数

int accept (int, struct sockaddr *__peer, socklen_t *);

第一个是socket描述子,第二个是用来接收的客户端地址,第三个是地址的大小。注意第三个是指针类型,所以要事先构造好大小的变量,然后传地址进去。

另外,《后台开发核心技术与应用实践》中的例子,第二个和第三个都传的NULL。我的理解是,如果不需要接收这两个量,就可以传一个空值进去。

accept函数会返回一个新的socket描述子,这个新的描述子代表了服务端和客户端的连接。后面可以用于读取数据以及关闭连接。

   //等待并接受连接
const int MAXBUF = 4096;
char buff[MAXBUF];
struct sockaddr_in client_addr;
int client_addr_len = sizeof(client_addr);
int client_fd;
while (1) {
client_fd = accept(fd, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_fd < 0) {
std::cout<<"connect error"<<std::endl;
continue;
}
//接收数据
//关闭套接字
}

从socket中读取字符:

服务器与客户端建立好连接之后,就可以调用网络I/O进行读写操作了。网络I/O操作有下面几组:

read()/write()

recv()/send()

readv()/writev()

recvmsg()/sendmsg()

recvfrom()/sendto()

具体的区别待补充,这里只举一个例子。

_ssize_t read (int __fd, void *__buf, size_t __nbyte);

__fd是刚才获得的服务器与客户端建立连接的socket描述子

__buf是缓冲区指针

__nbyte是缓冲区大小

        //接收数据
int nbytes = read(client_fd, buff, MAXBUF);
std::cout<<"get infomation: "<<buff<<std::endl;

关闭socket:

完成读写操作就要关闭相应的socket描述子,可以类比与文件完成读写操作之后也要关闭一样。

close函数

int     close (int __fildes);

只有一个参数,就是socket描述子。

close会把该socket标记为关闭,然后立即返回到调用进程。该描述子不能再由调用进程使用,即,不能再作为read或write的第一个参数。

但是,这里需要注意的是,close操作只是使相应socket描述子的引用-1,只有当引用计数为0时,才会出发TCP发送终止连接请求。

以上是服务器端的基本代码,总的代码如下:

#include <sys/socket.h>
#include <iostream>
#include <cygwin/in.h>
#include <cstring>
#include <unistd.h> int main() {
std::cout<<"running server"<<std::endl;
//创建TCP套接字
//AF_INET:网络连接,ipv4
//SOCK_STREAM:TCP连接
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd<0) {
std::cout<<"create socket error!"<<std::endl;
return 0;
}
std::cout<<"create socket: "<<fd<<std::endl; //命名套接字
struct sockaddr_in myaddr;
memset((void *)&myaddr, 0, sizeof(myaddr));
//关于htonl和htons,参考以下网页:ntohs, ntohl, htons,htonl的比较和详解
//https://blog.csdn.net/haoxiaodao/article/details/73162663
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
myaddr.sin_port = htons(6666);
if (bind(fd, (struct sockaddr*)&myaddr, sizeof(myaddr)) < 0) {
std::cout<<"name socket error!"<<std::endl;
return 0;
}
std::cout<<"name socket"<<std::endl; //创建监听队列
if (listen(fd, 5) < 0) {
std::cout<<"listen failed"<<std::endl;
return 0;
}
std::cout<<"=============listening, port = 6666"<<std::endl; //等待并接受连接
const int MAXBUF = 4096;
char buff[MAXBUF];
struct sockaddr_in client_addr;
int client_addr_len = sizeof(client_addr);
int client_fd;
while (1) {
client_fd = accept(fd, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_fd < 0) {
std::cout<<"connect error"<<std::endl;
continue;
}
//接收数据
int nbytes = read(client_fd, buff, MAXBUF);
std::cout<<"get infomation: "<<buff<<std::endl;
//关闭套接字
close(client_fd);
}
close(fd);
return 0;
}

相比与服务器端,客户端的区别主要在于连接指定计算机的端口和向socket中写入信息。

连接制定计算机的端口:

connect函数

int connect (int, const struct sockaddr *, socklen_t);

第一个参数是socket描述子,第二个是目标服务器的地址,第三个是地址struct的大小。

关于目标服务器地址的构造,需要利用函数inet_pton(),代码如下:

    const char* server = "127.0.0.1";
struct sockaddr_in server_addr;
memset((char*)&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(6666);
inet_pton(AF_INET, server, &server_addr.sin_addr);

构造完目标服务器的地址,就可以调用connect()函数了。

    if (connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
std::cout<<"connect failed"<<std::endl;
return 0;
}

向socket中写入信息:

跟上面接收信息对应,使用了write()函数

    const int MAXBUF = 4096;
char buffer[MAXBUF] = "hello TCP";
int nbytes = write(fd, buffer, 10);

以上就是客户端与服务器端相比不同的部分,客户端的基本代码如下:

#include <sys/socket.h>
#include <iostream>
#include <cstring>
#include <cygwin/in.h>
#include <arpa/inet.h>
#include <unistd.h> int main() {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
std::cout<<"socket error"<<std::endl;
return 0;
} const char* server = "127.0.0.1";
struct sockaddr_in server_addr;
memset((char*)&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(6666);
inet_pton(AF_INET, server, &server_addr.sin_addr); if (connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
std::cout<<"connect failed"<<std::endl;
return 0;
} const int MAXBUF = 4096;
char buffer[MAXBUF] = "hello TCP";
int nbytes = write(fd, buffer, 10); close(fd);
}

参考资料:

1. TCP套接字编程入门 https://blog.csdn.net/lihao21/article/details/64624796?locationNum=7&fps=1

2. 《后台开发核心技术与应用实践》

socket套接字编程(1)——基本函数的更多相关文章

  1. linux网络环境下socket套接字编程(UDP文件传输)

    今天我们来介绍一下在linux网络环境下使用socket套接字实现两个进程下文件的上传,下载,和退出操作! 在socket套接字编程中,我们当然可以基于TCP的传输协议来进行传输,但是在文件的传输中, ...

  2. linux网络编程-(socket套接字编程UDP传输)

    今天我们来介绍一下在linux网络环境下使用socket套接字实现两个进程下文件的上传,下载,和退出操作! 在socket套接字编程中,我们当然可以基于TCP的传输协议来进行传输,但是在文件的传输中, ...

  3. socket套接字编程 HTTP协议

    socket套接字编程  套接字介绍  1. 套接字 : 实现网络编程进行数据传输的一种技术手段  2. Python实现套接字编程:import  socket  3. 套接字分类 >流式套接 ...

  4. socket 套接字编程

    今日内容 socket 套接字编程 简易服务端与客户端代码实现 通信循环 黏包现象(TCP协议) 报头制作.struct 模块.封装形式 内容详细 一.socket 套接字编程 实现一款能够进行数据交 ...

  5. Linux之socket套接字编程20160704

    介绍套接字之前,我们先看一下传输层的协议TCP与UDP: TCP协议与UDP协议的区别 首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UD ...

  6. 基于TCP协议的socket套接字编程

    目录 一.什么是Scoket 二.套接字发展史及分类 2.1 基于文件类型的套接字家族 2.2 基于网络类型的套接字家族 三.套接字工作流程 3.1 服务端套接字函数 3.2 客户端套接字函数 3.3 ...

  7. 基于TCP连接的socket套接字编程

    基于TCP协议的套接字编程(简单) 服务端 import socket server = socket.socket() server.bind( ('127.0.0.1', 9999) ) serv ...

  8. day31 socket套接字编程

    为什么要有套接字编程? 在上节课的学习中,我们学习了OSI七层协议,但是如果每次进行编程时我们都需要一层一层的将各种协议使用在我们的程序中,这样编写程序实在是太麻烦了,所以为了让程序的编写更加的简单, ...

  9. 19、网络编程 (Socket套接字编程)

    网络模型 *A:网络模型 TCP/IP协议中的四层分别是应用层.传输层.网络层和链路层,每层分别负责不同的通信功能,接下来针对这四层进行详细地讲解. 链路层:链路层是用于定义物理传输通道,通常是对某些 ...

随机推荐

  1. mysql事务_事务隔离级别详解

    使用事务语法 1. 开启事务start transaction,可以简写为 begin 2. 然后记录之后需要执行的一组sql 3. 提交commit 4. 如果所有的sql都执行成功,则提交,将sq ...

  2. Ubuntu20.04编译ffmpeg

    1.安装编译所需工具,GCC 2.安装yasm nasm yasm和nasm是两个编译器,编译ffmpeg需要用到 安装命令: sudo aptitude install yasm nasm 3.安装 ...

  3. PHPer 面试

    A:怎么保证促销商品不会超卖? 答:这个问题是我们当时开发时遇到的一个难点,超卖的原因主要是下的订单的数目和我们要促销的商品的数目不一致导致的,每次总是订单的数比我们的促销商品的数目要多,当时我们的小 ...

  4. Head First 设计模式 —— 03. 装饰器 (Decorator) 模式

    思考题 有如下类设计: 如果牛奶的价钱上扬,怎么办?新增一种焦糖调料风味时,怎么办? 造成这种维护上的困难,违反了我们之前提过的哪种设计原则? P82 取出并封装变化的部分,让其他部分不收影响 多用组 ...

  5. BPOS关于“相邻库存查询”的调整

    "相邻库存查询"的应用场景:主要是实现门店间,相互查看商品库存状况,但出于公司对门店的查看权限控制要求,不能一次性查看到相关店铺的所有库存,所以产生了"相邻库存查询&qu ...

  6. C# 修改PNG图片metadata信息 (含转载fancyblogs博文)

    WPF中使用 metadata-extractor可以轻松获取 PNG图片metadata信息 NuGet 获取地址: PM> Install-Package MetadataExtractor ...

  7. 利用PHP递归 获取所有的上级栏目

    /** * 获取所有的上级栏目 * @param $category_id * @param array $array * @return array * @author 宁佳兵 <meilij ...

  8. 手把手教你搭建一个跟vue官方同款文档(vuepress)

    前言 VuePress 由两部分组成:第一部分是一个极简静态网站生成器 (opens new window),它包含由 Vue 驱动的主题系统和插件 API,另一个部分是为书写技术文档而优化的默认主题 ...

  9. Docker Harbor 高可用 1.7.5版本(七)

    环境说明: node1 10.10.5.135 仓库 1 node2 10.10.5.136 仓库 2 node3 10.10.5.137 客户端 实验内容: Harbor 可以在两台主机之间相互同步 ...

  10. iTerm2 实现 ssh 自动登录,并使用 Zmodem 实现快速传输文件

    原文链接:https://fuckcloudnative.io/posts/iterm2-auto-login/ 对于 YAML 工程师来说,我们经常需要 ssh 登录不同的服务器,每次登录时都要经历 ...