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. 搭建web攻防环境

    提示:本实验仅用于学习参考,不可用作其他用途! 任务一.基于centos7搭建dvwa web服务靶机 在centos7安装LAMP并启动,访问phpinfo页面 从互联网下载dvwa并解压到/var ...

  2. android stdio 打包

    1.Build -> Generate Signed APK...,打开如下窗口 2.假设这里没有打过apk包,点击Create new,窗口如下 这里只要输入几个必要项 Key store p ...

  3. CentOS8_在线安装_网络源_网络镜像源填写格式_以及其他笔记

    CentOS8_在线安装_网络源_网络镜像源填写格式_以及其他笔记 转载注明来源: 本文链接 来自osnosn的博客,写于 2020-10-1. 参考: Centos8.0.1905 在线安装源选择 ...

  4. filleSystemBasises

    基本查询命令 pwd 查看当前目录 dir 显示当前目录下的文件信息 more 查看文本文件的具体内容 cd 修改用户当前目录 mkdir 创建新的目录 rmdir 删除目录 copy filenam ...

  5. 冷饭新炒:理解Redisson中分布式锁的实现

    前提 在很早很早之前,写过一篇文章介绍过Redis中的red lock的实现,但是在生产环境中,笔者所负责的项目使用的分布式锁组件一直是Redisson.Redisson是具备多种内存数据网格特性的基 ...

  6. python中环境变量的使用

    前言 之前就经常用,今天来凑个篇数. 在开发的过程中,我们经常会将代码中某些可能更改的,比如redis地址,数据库地址,限流阈值等参数写活来提高灵活性, 传统的方式可能是写在配置文件中,比如 xml ...

  7. 【Java基础】基本语法-变量与运算符

    基本语法-变量与运算符 关键字和保留字 关键字定义:被 Java 语言赋予了特殊含义,用做专门用途的字符串(单词). 关键字特点:关键字中所有字母都为小写. 用于定义数据类型:class.interf ...

  8. http-请求和响应报文的构成

    请求的构成: 1)请求方法URI协议/版本 2)请求头(Request Header) 3)请求正文 1)请求方法URI协议/版本 Request URL: http://localhost:8080 ...

  9. spring cloud config —— git配置管理

    目录 talk is cheep, show your the code Server端 pom.xml server的application.yml 配置文件 测试Server client端 po ...

  10. 安装MySQL数据库(在Windows下通过zip压缩包安装)

    安装MySQL 这里建议大家使用压缩版,安装快,方便.不复杂. 软件下载 mysql5.7 64位下载地址: https://dev.mysql.com/get/Downloads/MySQL-5.7 ...