端口号:
端口号(port)是传输层协议的内容.
1. 端口号是一个二字节16位的整数;
2. 端口号是用来表示一个进程,表示当前数据交给哪一个进程来处理;
3. IP地址 + 端口号可以网络上某一台主机的某一个进程;
4. 一个端口号只能被一个进程占用;
注意: 一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定
TCP协议
TCP(Transmission Control Protocol 传输控制协议)
- 传输层协议
- 有连接
- 可靠连接
- 面向字节流
UDP协议
UDP(User Datagram Protocol 用户数据报协议) - 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
socket编程接口
创建socket文件描述符(tcp/udp,客户端+服务端)
int socket(int domain, int type, int protocol);
Domain:地址域
Sock_STREAM流式套接字,默认协议TCP,不支持UDP
Sock_DGRAM 数据报套接字,默认协议UDP,不支持TCP
Type: 套接字类型
0:使用默认类型协议
Protocol:协议类型
绑定端口号 (TCP/UDP, 服务器)
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
Sockfd: 创建套接字返回的描述符
Addr: 地址信息
Addrlen: 地址信息长度
返回值:0 失败:-1
socklen_t address_len);
建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
发送数据
Send()
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
Sockfd: 操作句柄,套接字描述符
Buf: 想要发送的数据
Len: 要发送的数据长度(不一定是字节长度)
Flags: 0--默认阻塞发送
Daddr: 目的段地址信息--标识数据要发送到哪里去
Addrlen:实际发送的数据长度 失败:-1
接受数据
Recv(sockfd, buf, len, flag)
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//名称 目的
AF_INET IPv4网络通信
|AF_INET6 IPv6网络通信
AF_PACKET 链路层通信
AF_UNIX, AF_LOCAL 本地通信
type
SOCK_ STREAM 字节流套接字
SOCK DGRAM 数据报套接字
SOCK_ .SEQPACKET 有序分组套接字
SOCK_ RAW 原始套接字
protocol
IPPROTO_TCP TCP传输协议
IPPTOTO_UDP UDP传输协议
IPPROTO_SCTP STCP传输协议
IPPROTO_TIPCTCP TIPC传输协议
字节序转换函数
数字的字节序转换: 主机字节序转换为网络字节序
4个字节的数据
uint32_t htonl(uint32_t hostlong);
2个字节的数据(不可用4字节转换)
uint16_t htons(uint16_t hostshort);
数字的字节序转换: 网络字节序转换为主机字节序
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
将点分十进制IP地址字符串转换为网络字节序IP地址
in_addr_t inet_addr(const char *cp);
int inet_pton(int af, const char *src, void *dst);
将网络字节序转换为字符串点分式十进制IP地址
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
char *inet_ntoa(struct in_addr in);
快速判断断开
原理:
TCP的连接内部管理中,内建有保活机制;
当长时间没有数据往来的时候,每隔一段时间向对方发送一个保活探测包,要求对方回复,当多次发送的保活探测包都没有响应,则认为连接断开
表现:
若连接断开,则recv会返回0,send会触发异常SIGPIPE
recv返回0,不是没有数据的意思.而指的是连接断开
因为TCP面向字节流.
封装TCP函数TcpScoket.hpp
| | 1 #include <stdio.h>
| | 2 #include <string>
| | 3 #include <string.h>
| | 4 #include <unistd.h>
| | 5 #include <sys/socket.h>
| | 6 #include <stdlib.h>
| | 7 #include <netinet/in.h>
| | 8 #include <arpa/inet.h>
| | 9 #include <errno.h>
| | 10 #include <iostream>
| | 11 //创建socket文件描述符(tcp/udp,客户端+服务端)
| | 12 // int socket(int domain, int type, int protocol);
| | 13 //建立连接 (TCP, 客户端)
| | 14 // int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
| | 15 //绑定端口号 (TCP/UDP, 服务器)
| | 16 // int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
| | 17 // socklen_t address_len);
| | 18 //开始监听socket (TCP, 服务器)
| | 19 // int listen(int socket, int backlog);
| | 20 //建立连接 (TCP, 客户端)
| | 21 // int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
| | 22 // 接收请求 (TCP, 服务器)
| | 23 // int accept(int socket, struct sockaddr* address, socklen_t* address_len);
| | 24 //名称 目的
| | 25 //AF_INET IPv4网络通信
| | 26 //AF_INET6 IPv6网络通信
| | 27 //AF_PACKET 链路层通信
| | 28 //AF_UNIX, AF_LOCAL 本地通信
| | 29
| | 30 //type
| | 31 //SOCK_ STREAM 字节流套接字
| | 32 //SOCK DGRAM 数据报套接字
| | 33 //SOCK_ .SEQPACKET 有序分组套接字
| | 34 //SOCK_ RAW 原始套接字
| | 35
| | 36 //protocol
| | 37 //IPPROTO_TCP TCP传输协议
| | 38 //IPPTOTO_UDP UDP传输协议
| | 39 //IPPROTO_SCTP STCP传输协议
| | 40 //IPPROTO_TIPCTCP TIPC传输协议
| | 41
| | 42 #define CHECK_RET(q) if((q) == false){return -1;}
| | 43
| | 44 typedef struct calculator_info_t {
| | 45 int num1;
| | 46 int num2;
| | 47 char str[30];
| | 48 char op;
| | 49 }calculator_info;
| | 50
| | 51 class TcpSocket
| | 52 {
| | 53 public:
| | 54 TcpSocket(): _sockfd(-1){
| | 55 }
| | 56 void SetSockFd(int fd){
| | 57 _sockfd = fd;
| | 58 }
| | 59 int GetSockFd(){
| | 60 return _sockfd;
| | 61 }
| | 62 bool Socket(){
| | 63 _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
| | 64 if(_sockfd < 0){
| | 65 perror("socket errno\n");
| | 66 return false;
| | 67 }
| | 68 return true;
| | 69 }
| | 70 bool Bind(std::string &ip, uint16_t port){
| | 71 struct sockaddr_in addr;
| | 72 //struct sockaddr_in {
| | 73 //short int sin_family; //地址族,AF_xxx 在socket编程中只能是AF_IN
| | 74 //unsigned short int sin_port; // 端口号 (使用网络字节顺序)
| | 75 //struct in_addr sin_addr; //存储IP地址 4字节
| | 76 //unsigned char sin_zero[8]; // 总共8个字节,实际上没有什么用,只>
| | 77 //};
| | 78 addr.sin_family = AF_INET;
| | 79 //网络字节顺序与本地字节顺序之间的转换函数:
| | 80 // htonl()–“Host to Network Long”
| | 81 // ntohl()–“Network to Host Long”
| | 82 // htons()–“Host to Network Short”
| | 83 // ntohs()–“Network to Host Short”
| | 84 addr.sin_port = htons(port);
| | 85 addr.sin_addr.s_addr = inet_addr(ip.c_str());
| | 86 //数字的字节序转换: 主机字节序转换为网络字节序
| | 87 //4个字节的数据
| | 88 // uint32_t htonl(uint32_t hostlong);
| | 89 //2个字节的数据(不可用4字节转换)
| | 90 // uint16_t htons(uint16_t hostshort);
| | 91 //数字的字节序转换: 网络字节序转换为主机字节序
| | 92 // uint32_t ntohl(uint32_t netlong);
| | 93 // uint16_t ntohs(uint16_t netshort);
| | 94 //将点分十进制IP地址字符串转换为网络字节序IP地址
| | 95 // in_addr_t inet_addr(const char *cp);
| | 96 // int inet_pton(int af, const char *src, void *dst);
| | 97 //将网络字节序转换为字符串点分式十进制IP地址
| | 98 // const char *inet_ntop(int af, const void *src, char *dst, sockl
| | 99 // char *inet_ntoa(struct in_addr in);
| |100
| |101 socklen_t len = sizeof(struct sockaddr_in);
| |102 int ret = bind(_sockfd, (const struct sockaddr *)&addr,len);
| |103 if(ret < 0){
| |104 perror("bind errno\n");
| |105 return false;
| |106 }
| |107 return true;
| |108 }
| |109 bool Listen(int backlog = 10){
| |110 // int listen(int socket, int backlog);
| |111 //backlog:最大并发连接数--内核中已完成连接队列的最大节点数
| |112 int ret = listen(_sockfd,backlog);
| |113 if(ret < 0){
| |114 perror("listen errno\n");
| |115 return false;
| |116 }
| |117 return true;
| |118 }
| |119 bool Connect(std::string &ip,uint16_t port){
| |120 //int connect(int sock, const struct sockaddr *addr, socklen_t ad
| |121 //addr: 要连接的服务器地址信息
| |122 struct sockaddr_in addr;
| |123 addr.sin_family = AF_INET;
| |124 addr.sin_port = htons(port);
| |125 addr.sin_addr.s_addr = inet_addr(ip.c_str());
| |126 socklen_t len = sizeof(struct sockaddr_in);
| |127
| |128 int ret = connect(_sockfd,(struct sockaddr*)&addr, len);
| |129 if(ret < 0){
| |130 perror("connect errno\n");
| |131 return false;
| |132 }
| |133 else{
| |134 std:: cout<<"connect success\n";
| |135 return true;
| |136 }
| |137 }
| |138 bool Accept(TcpSocket &csock, struct sockaddr_in *addr = NULL){
| |139 //在一个套接字上接受一个连接
| |140 //int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
| |141 //addr: 客户端地址信息
| |142 //len: 输入输出参数,既要指定接受长度,还要接收实际长度
| |143 //返回值: 为新客户新建的socket套接字描述符
| |144 //通过这个返回的描述符可以与指定的客户端进行通信
| |145 struct sockaddr_in _addr;
| |146 socklen_t len = sizeof(struct sockaddr_in);
| |147 int newfd = accept(_sockfd,(struct sockaddr*)&_addr, &len);
| |148 if (newfd < 0){
| |149 perror("accept errno\n");
| |150 return false;
| |151 }
| |152 if (addr != NULL){
| |153 memcpy(addr, &_addr, len);
| |154 }
| |155 csock.SetSockFd(newfd);
| |156 //_socket--仅用于接受新客户端连接请求
| |157 //newfd--专门用于与客户端进行通信
| |158 return true;
| |159 }
| |160 bool Recv(std::string &buf){
| |161 char tmp[4096] = {0};
| |162 //ssize_t recv(int sockfd, void *buf, size_t len, int flags);
| |163 //flags:0--默认阻塞接受 MSG_PEEK-获取数据但是不从缓冲区移除
| |164 //返回值:实际接受的数据长度 失败:-1 断开连接: 0
| |165 int ret = recv(_sockfd, tmp,4096,0);
| |166 if (ret < 0){
| |167 perror("recv error");
| |168 return false;
| |169 }
| |170 else if (ret == 0){
| |171 printf("peer shutdown\n");
| |172 return false;
| |173 }
| |174 buf.assign(tmp, ret);
| |175 return true;
| |176 }
| |177 bool Send(std::string &buf){
| |178 //int send(int s, const void *msg, size_t len, int flags);
| |179 int ret = send(_sockfd, buf.c_str(), buf.size(), 0);
| |180 if (ret < 0){
| |181 perror("send error");
| |182 return false;
| |183 }
| |184 return true;
| |185 }
| |186 bool Close(){
| |187 close(_sockfd);
| |188 _sockfd = -1;
| |189 }
| |190 // ~TcpSocket() {}
| |191 private:
| |192 int _sockfd;
| |193 };
Tcp_Server.cpp
1 /**********************************************************
2 * Author : yang
3 * Email : wk_eternity@163.com
4 * Last modified : 2019-07-18 10:02
5 * Filename : Tcp_Server.cpp
6 * Description : 基于封装的tcpsocket完成tcp服务端程序pp
7 * 1.创建套接字
8 * 2.绑定地址信息
9 * 3.开始监听
10 * 4.获取已经连接成功的客户端socket
11 * 5.接受数据
12 * 6.发送数据
13 * 7.关闭套接字
14 * *******************************************************/
15
16 #include <iostream>
17 #include "TcpSocket.hpp"
18
19 int main(int argc,char *argv[])
20 {
21 if(argc != 3){
22 printf("./tcp_srv ip port\n");
23 return -1;
24 }
25 std::string ip = argv[1];
26 uint16_t port = atoi(argv[2]);
27
28 TcpSocket sock;
29 CHECK_RET(sock.Socket());
30 CHECK_RET(sock.Bind(ip,port));
31 CHECK_RET(sock.Listen());
32
33 char buf[1024];
34 while(1){
35 TcpSocket clisock;
36 struct sockaddr_in cliaddr;
37 //accept是阻塞获取已经完成的连接
38 if (sock.Accept(clisock,&cliaddr) == false){
39 continue;
40 }
41 printf("new connect client:%s:%d\n",inet_ntoa(cliaddr.sin_addr), ntohs
42
43 int fd = clisock.GetSockFd();
44 //calculator_info info;
45 char buf[1024];
46 while(1){
47 fflush(stdout);
48 //recv(fd, &info,sizeof(calculator_info),0);
49 recv(fd, buf,sizeof(buf),0);
50 //std::cout<<"clinet says:"<<info.str<<std::endl;
51 std::cout<<"clinet says:"<<buf<<std::endl;
52 //printf("num1:[%d] %c num2[%d] = [%d]\n",info.num1, info.op, info
53
54 fflush(stdin);
55 std::cin>>buf;
56 send(fd, buf,sizeof(buf),0);
57 }
58 }
59 sock.Close();
60 return 0;
61 }
Tcp_client.cpp
1 /**********************************************************
2 * Author : yang
3 * Email : wk_eternity@163.com
4 * Last modified : 2019-07-18 16:34
5 * Filename : Tcp_client.cpp
6 * Description : 使用封装的tcpsocket类实现tcp客户端程序
7 * 1.创建套接字
8 * 2.向服务端发起连接请求
9 * 3.发送数据
10 * 4.接受数据
11 * 5.关闭套接字
12 * *******************************************************/
13 #include <iostream>
14 #include "TcpSocket.hpp"
15
16 int main(int argc,char *argv[])
17 {
18 if(argc != 3){
19 std::cout<<".tcp_cli ip port\n";
20 return -1;
21 }
22 std::string ip = argv[1];
23 uint16_t port = atoi(argv[2]);
24
25 TcpSocket sock;
26 CHECK_RET(sock.Socket());
27 CHECK_RET(sock.Connect(ip, port));
28
29 char buf[1024];
30 while(1) {
31 calculator_info info;
32 info.num1 = 11;
33 info.num2 = 12;
34 info.op = '+';
35
36 //strcpy(info.str,"client 666");
37 fflush(stdin);
38 std::cin>>buf;
39 int fd = sock.GetSockFd();
40 send(fd, buf, sizeof(buf), 0);
41
42 fflush(stdout);
43 recv(fd, buf,sizeof(buf), 0);
44 std::cout<<"server says:"<<buf<<std::endl;
45 }
46 sock.Close();
47 return 0;
48 }
执行结果:
[test@localhost my]$ ./Tcp_Server 127.0.0.1 10099
new connect client:127.0.0.1:51154
clinet says:1
2
clinet says:3
4
clinet says:5
6
clinet says:7
[test@localhost my]$ ./Tcp_client 127.0.0.1 10099
connect success
1
server says:2
3
server says:4
5
server says:6
7