第13章 TCP编程(1)_socket套接字
1. socket套接字
(1)套接字简介
①socket是一种通讯机制,它包含一整套的调用接口和数据结构的定义,它给应用进程提供了使用如TCP/UDP等网络协议进行网络通讯的手段。
②Linux中的网络编程通过socket接口实现。socket是一种特殊的IO,提供对应的文件描述符。
③一个完整的socket都有一个相关描述(协议、本地地址、本地端口、远程地址和远程端口等)。
④每一个套接字有一个本地的唯一socket,由操作系统分配。
(2)创建socket
头文件 |
#include <sys/socket.h> |
函数 |
int socket(int domain, int type, int protocol); |
参数 |
domain参数: ①AF_INET: IPv4因特网域 ②AF_INET6: IPv6因特网域 ③AF_UNIX: unix域 ④AF_UNSPEC: 未指定 type参数: ①SOCK_STREAM: 流式的套接字可以提供可靠的、面向连接的通讯流。它使用了TCP协议,TCP保证了数据传输的正确性和顺序性。 ②SOCK_DGRAM:数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠及无差错。 ③SOCK_RAW:原始套接字允许对低层协议(如IP或ICMP)直接访问,主要用于新的网络协议实现的测试等。 ④SOCK_SEQPACKET: 长度固定、有序、可靠的面各连接报文传递。 protocol参数:通常为0,表示按给定的域和套接字类型选择默认协议。 |
功能 |
创建套接字 |
返回值 |
成功返回内核中消息队列的标识ID,出错返回-1 |
备注 |
套接字创建在内核中,若创建成功则返回内核文件描述表中的socket描述符。 |
2. 字节序、地址结构和IPv4地址族
(1)字节序
①不同体系结构的主机使用不同的字节序存储器来保存多字节整数。字节存储顺序不同,有的系统是高位在前,低位在后,而有的系统是低位在前,高位在后。
②字节序分为大端(高位存储于低地址,低位存储于高地址)和小端(高位存储于高地址,低位存储于低地址)
③网络协议使用网络字节序,即大端字节序。
(2)字节序转换函数
①uint32_t htonl(unit32_t hostlong); //将32位整数从主机字节序转为网络字节序
②uint16_t htons(unit16_t hostshort);//将16位整数从主机字节序转为网络字节序③uint32_t ntohl(unit32_t netlong); //将32位整数从网络字节序转为主机字节序
④uint16_t ntohs(unit16_t netshort);//将16位整数从网络字节序转为主机字节序
(3)通用地址结构
头文件 |
#include <sys/socket.h> |
结构体 |
struct sockaddr{ unsigned short sa_family; //internet地址族,AF_XXX char sa_data[14]; //14字节的协议地址 } |
备注 |
①sa_data包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一起的。 ②sa_family一般来说,IPv4使用AF_INET。 ③在传递给需要地址结构的函数时,把指向该结构体的指针转换成(struct sockaddr*)传递进去。 |
(4)因特网地址结构(专用地址结构)
头文件 |
#include <sys/socket.h> |
结构体 |
struct in_addr{ //其中的in指的是internet in_addr_t s_addr //ipv4地址 } struct sockaddr_in{ short int sin_family; //internet地址族,AF_XXX(主机字节序) unsigned short int sin_port; //端口号,16位(网络字节序) struct in_addr sin_addr; //32位的IPv4地址(网络字节序) unsigned char sin_zero[8]; //添0(为了格式对齐的填充位) } |
备注 |
sockaddr和sockaddr_in这两个数据类型是等效的,可以相互转换,通常使用sockaddr_in更为方便。 |
(5)IPv4地址族和字符地址间的转换
头文件 |
#include <arp/inet.h> |
函数1 |
const char* inet_ntop(int domain, const void* addr, char* str, socklen_t size);//网络字节地址转为点分十进制 返回:成功返回地址字符串指针,出错返回NULL |
函数2 |
int inet_pton(int domain, const char* str, void* addr);//点分转网络 返回:成功返回1,无效格式返回0,出错返回-1 |
参数 |
domain:Internet地址族,如AF_INET addr: internet地址,32位IPv4地址(网络字节序) str: 地址字符串(点分十进制)指针 size: 地址字符串大小 |
(6)填写IPv4地址族结构体案例
struct sockaddr_in sin; //定义一个sockaddr_in结构体
char buf[]; memset(&sin, , sizeof(sin));
sin.sin_family = AF_INET; //填写Internet地址族
sin.sin_port = htons((short)); //填写端口号(网络字节序) //填充sin_addr
if(inet_pton(AF_INET, "192.168.2.1", &sin.sin_addr.s_addr) <=){
//错误处理
} printf("%s\n", inet_ntop(AF_INET, &sin.sin_addr.s_addr,buf, sizeof(buf)));
3. TCP编程模型
3.1 TCP通讯模型
(1)服务端调用序列
①调用socket函数创建套接字
②调用bind绑定本地地址和端口
③调用listen启动监听
④调用access从己连接队列中提取客户连接
⑤调用I/O函数(read/write)与客户端通讯
⑥调用close关闭套接字
(2)客户端调用序列
①调用socket函数创建套接字
②调用connect连接服务器端
③调用I/O函数(read/write)与服务器端通讯
④调用close关闭套接字
3.2 相关函数
(1)套接字与地址绑定
头文件 |
#include <sys/socket.h> |
绑定地址 |
int bind(int sockfd, const struct sockaddr* addr, socklen_t len); 返回:成功返回0,出错返回-1 特特地址:#define INADDR_ANY (uint32_t)0x00000000 //响应主机上的任何一个可用的IP地址。 |
查找绑定到套接字的地址 |
int getsockname(int sockfd, struct sockaddr* addr, socklen_t alenp) 返回:成功返回0,出错返回-1 |
获取对方地址 |
int getpeername(int sockfd, struct sockaddr* addr, socklen_t alenp) 返回:成功返回0,出错返回-1 |
(2)建立连接
头文件 |
#include <sys/socket.h> |
服务器端 |
①int listen(int sockfd, int backlog);//backlog为指定进行客户端连接排队的队列长度。 返回:成功返回0,出错返回-1 ②int accept(int sockfd, struct sockaddr* addr, socklen_t* len); |
客户端 |
int connect(int sockfd, cosnt struct sockaddr* addr, socklen_t len) 返回:成功返回0,出错返回-1 |
【编程实验】获得服务器的时间
//time_tcp_server.c(服务端)
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <time.h>
#include <signal.h> /*获取服务器端的当前时间*
测试:telnet 127.0.0.1 xxxx
http://xxx.xxx.xxx.xxx:端口号
注意:演示时可关闭服务器的防火墙,防止端口被过滤
#service iptables status 查看防火墙
#service iptables stop 关闭防火墙
*/ int sockfd;
int bStop = ; void sig_handler(int signo)
{
if(signo == SIGINT){
bStop = ;
printf("server close\n"); exit();
}
} //显示客户端信息
void out_addr(struct sockaddr_in* addr)
{
//将端口从网络字节序转换成主机字节序
int port = ntohs(addr->sin_port);
//获得IP地址
char ip[] ={};
//将ip地址从网络字节序转换成点分十分制
inet_ntop(AF_INET, &addr->sin_addr.s_addr, ip, sizeof(ip)); printf("Client: %s(%d) connected\n", ip, port);
} //服务程序
void do_service(int fd)
{
//获得系统时间
long t = time();
char* s = ctime(&t);
size_t size = strlen(s) * sizeof(char); //将服务器端获得的系统时间写回客户端
if(write(fd, s, size) != size){
perror("write error");
}
} int main(int argc, char* argv[])
{
if(argc < ){
printf("usage: %s port\n", argv[]);
} //按ctrl-c时中止服务端程序
if(signal(SIGINT, sig_handler) == SIG_ERR){
perror("signal sigint error");
exit();
} /*步骤1:创建socket(套接字)
*注:socket创建在内核中,是一个结构体
*AF_INET:IPv4
*SOCK_STREAM:tcp协议
*/
sockfd = socket(AF_INET, SOCK_STREAM, ); /*步骤2:将sock和地址(包括ip、port)进行绑定*/
struct sockaddr_in servAddr; //使用专用地址结构体
memset(&servAddr, , sizeof(servAddr));
//往地址中填入ip、port和Internet地址族类型
servAddr.sin_family = AF_INET;//IPv4
servAddr.sin_port = htons(atoi(argv[])); //port
servAddr.sin_addr.s_addr = INADDR_ANY; //任一可用的IP if(bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < ){
perror("bind error");
exit();
} /*步骤3:调用listen函数启动监听
* 通知系统去接受来自客户端的连接请求
*/
if(listen(sockfd, ) < ){ //队列中最多允许10个连接请求
perror("listen error");
exit();
} /*步骤4:调用accept函数,从请求队列中获取一个连接
* 并返回新的socket描述符
* */
struct sockaddr_in clientAddr;
socklen_t clientAddr_len = sizeof(clientAddr);
while(!bStop){
//如果没有客户端连接,调用此函数后会阻塞,直至获得一个客户端连接
int fd = accept(sockfd, (struct sockaddr*)&clientAddr, &clientAddr_len); if(fd < ){
perror("accept error");
continue;
} //输出客户端信息
out_addr(&clientAddr); /*步骤5:调用IO函数(read/write)和客户端进行双向通信*/
do_service(fd); /*步骤6: 关闭fd套接字*/
close(fd);
} close(sockfd); return ;
}
/*输出结果
* [root@localhost 13.TCP]# bin/time_tcp_server 8888
* Client: 192.168.32.100(40672) connected
* Client: 127.0.0.1(40608) connected
* Client: 192.168.32.100(40674) connected
*/
//time_tcp_client.c(客户端)
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h> int main(int argc, char* argv[])
{
if(argc < ){
printf("usage: %s ip port\n", argv[]);
exit();
} /*步骤1: 创建socket(套接字)*/
int sockfd = socket(AF_INET, SOCK_STREAM, );
if(sockfd < ){
perror("socket error");
} //往servAddr中填入ip、port和地址族类型
struct sockaddr_in servAddr;
memset(&servAddr, , sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(atoi(argv[]));
//将ip地址转换成网络字节序后填入servAdd中
inet_pton(AF_INET, argv[], &servAddr.sin_addr.s_addr); /*步骤2: 客户端调用connect函数连接到服务器端*/
if(connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < ){
perror("connect error");
exit();
} /*步骤3: 调用IO函数(read/write)和服务端进行双向通信*/
char buffer[];
memset(buffer, , sizeof(buffer));
size_t size;
if((size = read(sockfd, buffer, sizeof(buffer))) < ){
perror("read error");
} if(write(STDOUT_FILENO, buffer, size) != size){
perror("write error");
} /*关闭套接字*/
close(sockfd);
}
/*输出结果
[root@localhost 13.TCP]# bin/time_tcp_client 127.0.0.1 8888
Fri Mar 17 16:35:57 2017
*/
第13章 TCP编程(1)_socket套接字的更多相关文章
- 第13章 TCP编程(4)_基于自定义协议的多线程模型
7. 基于自定义协议的多线程模型 (1)服务端编程 ①主线程负责调用accept与客户端连接 ②当接受客户端连接后,创建子线程来服务客户端,以处理多客户端的并发访问. ③服务端接到的客户端信息后,回显 ...
- 第13章 TCP编程(3)_基于自定义协议的多进程模型
5. 自定义协议编程 (1)自定义协议:MSG //自定义的协议(TLV:Type length Value) typedef struct{ //协议头部 ];//TLV中的T unsigned i ...
- 第13章 TCP编程(2)_TCP的连接和关闭过程
4. TCP的连接和关闭过程 4.1 TCP连接的三次握手和四次挥手 (1)三次握手 ①第1次握手:建立连接.客户端发送连接请求报文段(SYN=1,sequence Number=x):然后客户端进入 ...
- Python黑帽编程2.8 套接字编程
Python黑帽编程2.8 套接字编程 套接字编程在本系列教程中地位并不是很突出,但是我们观察网络应用,绝大多数都是基于Socket来做的,哪怕是绝大多数的木马程序也是如此.官方关于socket编程的 ...
- Unix网络编程--卷一:套接字联网API
UNIX网络编程--卷一:套接字联网API 本书面对的读者是那些希望自己编写的程序能够使用成为套接字(socket)的API进行彼此通信的人. 目录: 0.准备环境 1.简介 2.传输层:TCP.UD ...
- Linux网络编程:原始套接字简介
Linux网络编程:原始套接字编程 一.原始套接字用途 通常情况下程序员接所接触到的套接字(Socket)为两类: 流式套接字(SOCK_STREAM):一种面向连接的Socket,针对于面向连接的T ...
- VC++学习之网络编程中的套接字
VC++学习之网络编程中的套接字 套接字,简单的说就是通信双方的一种约定,用套接字中的相关函数来完成通信过程.应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问 ...
- 网络编程与socket套接字
网络编程与socket套接字 传输层 PORT协议 port是一种接口,数据通过它在计算机和其他设备(比如打印机,鼠标,键盘或监视器)之间,网络之间和其他直接连接的计算机之间传递 TCP协议 传输 ...
- 网络编程之基于tcp和udp的套接字
一 udp协议网络编程 DNS:将域名解析成ip地址 SOCK_DGRAM:数据报协议,也是udp协议 udp协议的网络编程的一些用法: recvfrom:接收消息,接收的时一个元组,元组里面的元 ...
随机推荐
- cool
Heidi Roizen女士一度是硅谷人人争相学习的典范.她曾创办自己的公司并管理了 14 年之久.后来,她担任苹果公司主管开发者关系的高级副总裁.现在,她是 DFJ Venture 的一位风投家,她 ...
- Redis学习第八课:Redis高级实用特性(二)
Redis高级实用特性 4.持久化机制 Redis是一个支持持久化的内存数据库,也就是说Redis需要经常将内存中的数据同步到硬盘来保证持久化.Redis支持两种持久化方式:(1).snapshott ...
- JAVA中static什么作用?
是静态修饰符,什么叫静态修饰符呢?大家都知道,在程序中任何变量或者代码都是在编译时由系统自动分配内存来存储的,而所谓静态就是指在编译后所分配的内存会一直存在,直到程序退出内存才会释放这个空间,也就是只 ...
- SharePoint 设置Library中文档的默认打开方式
在SharePoint Library中的文档, 如word, excel等, 文档有两种打开方式, 一种是Viewer in Browser, 一种是Open in Client applicati ...
- 【opencv基础】图像翻转cv::flip详解
前言 在opencv中cv::flip函数用于图像翻转和镜像变换. 具体调用形式 void cv::flip( cv::InputArray src, // 输入图像 cv::OutputArray ...
- vec2d
namespace : cv::vec2d; void src2ipm(cv::Mat &srcimage, cv::Mat& uvgrid, cv::Mat& outimag ...
- chapter02 svm对手写体数字的数码图像进行识别
#coding=utf8 # 从sklearn.datasets里导入手写体数字加载器. from sklearn.datasets import load_digits # 从sklearn.cro ...
- URAL - 1003:Parity (带权并查集&2-sat)
Now and then you play the following game with your friend. Your friend writes down a sequence consis ...
- Codeforces gym101955 A【树形dp】
LINK 有n个大号和m个小号 然后需要对这些号进行匹配,一个大号最多匹配2个小号 匹配条件是大号和小号构成了前缀关系 字符串长度不超过10 问方案数 思路 因为要构成前缀关系 所以就考虑在trie树 ...
- 第三周作业3——Bug Report
作业要求来自:https://edu.cnblogs.com/campus/nenu/SWE2017FALL/homework/957 要求1: 准备工作:利用老师提供的git 命令,批量pull所有 ...