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. 自家公司关于git commit 的规范

    代码提交的commit info提个建议,fix的issue是哪个issue?都要有明确的链接.推荐方式:1.建立issue,说明问题的背景和原因.http://git.startdt.net/pay ...

  2. 一行 CSS 代码的魅力

    之前在知乎看到一个很有意思的讨论 一行代码可以做什么? 那么,一行 CSS 代码又能不能搞点事情呢? CSS Battle 首先,这让我想到了,年初的时候沉迷的一个网站 CSS Battle .这个网 ...

  3. Rejecting mapping update to [xxx] as the final mapping would have more than 1 type: [xxx, xx]

    说明: 1.elasticsearch 版本 6.3.1 2.在同一个index下创建两个type时报错,信息如下: 在创建第二个type:solr时,先前已经在相同索引下创建了一个type:es [ ...

  4. 风炫安全web安全学习第三十五节课 文件下载和文件读取漏洞

    风炫安全web安全学习第三十五节课 文件下载和文件读取漏洞 0x03 任意文件下载漏洞 一些网站由于业务需求,往往需要提供文件下载功能,但若对用户下载的文件不做限制,则恶意用户就能够下载任意敏感文件, ...

  5. spark知识点_datasources

    来自官网DataFrames.DataSets.SQL,即sparkSQL模块. 通过dataframe接口,sparkSQL支持多种数据源的操作.可以把dataframe注册为临时视图,也可以通过关 ...

  6. Hive表的基本操作

    目录 1. 创建表 2. 拷贝表 3. 查看表结构 4. 删除表 5. 修改表 5.1 表重命名 5.2 增.修.删分区 5.3 修改列信息 5.4 增加列 5.5 删除列 5.6 修改表的属性 1. ...

  7. linux find命令用法大全

    本文档格式如下,命令说明在命令的上一行,尝试使用 Ctrl+f 组合键快速在页面内搜索. 命令说明 命令     参数起始目录:查找文件的起始目录.实例# 当前目录搜索所有文件,文件内容 包含 &qu ...

  8. oracle数据库psu升级(本实验是将10.2.0.3.12升级到10.2.0.3.15)

    psu升级(本实验是将10.2.0.3.12升级到10.2.0.3.15) 一.解压安装包自定义存放路径为/home/oracle/yjb/psu/10.2.0.3.15cd /home/oracle ...

  9. 【剑指 Offer】03.数组中重复的数字

    题目描述 找出数组中重复的数字. 在一个长度为 n 的数组 nums 里的所有数字都在 0-n-1 的范围内.数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次.请找出数组中 ...

  10. Jmeter(三十五) - 从入门到精通进阶篇 - 关联(详解教程)

    1.简介 上一篇中介绍了如果想要同时发送多条请求,那么怎样才能让每条数据某些请求参数改变呢.这就用到了jMeter参数化.在实际测试场景中,我们往往还有这样的需求,登录后服务器响应的token作为下次 ...