TCP编程学习
TCP:(Transfer Control protocol,传输控制协议) 提供面向连接的,一对一的可靠数据传输的协议
Socket
1-socket是一个应用编程的接口,它是一种特殊的文件描述符。可以对它执行I0的操作函数,比如,read0,write(),close()等操作函数。
2-socket代表着网络编程的一种资源
3-socket的类型:
流式套接字(SOCK_STREAM)
唯一对应着TCP,提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
数据报套接字(SOCK DGRAM)
唯一对应着UDP,提供无连接服务。数据包以独立数据包的形式被发送,不提供无差保证,数据可能丢失或重复,顺序发送,可能乱序接收.
原始套接字(SOCK RAW)(对应着多个协议,发送穿透了传输层)
可以对较低层次协议如IP、ICMP直接访问。
IP地址
IP地址是Internet中主机的标识
Internet中的主机要与别的机器通信必须具有一个IP地址IP地址为32位 (IPv4) 或者128位 (IPv6)
每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由
IP地址分为IPV4和IPV6
IPV4:采用32位整数表示
IPV6:采用128位整数表示
端口号
16位的数字 (1-65535)
众所周知端口: 1~1023 (FTP: 21,SSH: 22, HTTP:80, HTTPS:469)保留端口: 1024-5000(不建议使用)可以使用的: 5000~65535
TCP端口和UDP端口是相互独立的
网络里面的通信是由 IP地址+端口号 来决定
字节序
PC一般采用的是小端模式,网络上数据传输使用的大端模式。
主要是IP地址和端口号要注意字节序调整,其他的也就那样了。
原因:套接字地址结构仅供本机TCP协议记录套集资信息而用,这个结构体变量本身是不在网络上传输的,但是其中某些内容,如IP地址和端口号是要在网络上传输的。
socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol) ;
参数:
domain:
AF_INET:IPy4 Internet protocolsI
AF_INET6:Pv6 Internet protocols
type:
SOCK_STREAM: 流式套接字 唯一对应于TCP
SOCK DGRAM:数据报套接字,唯一对应着UDP
SOCK RAW:原始套接字
protocol:
一般填0,原始套接字编程时需填充
bind
#include<sys/types .h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:
sockfd: 通过socket () 函数拿到的fd
addr: struct sockaddr的结构体变量的地址
addrlen: 地址长度
listen:把主动套接字变成被动套接字
#include <sys/types .h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数:
sockfd: 通过socket ()函数拿到的fd
backlog: 同时允许几路客户端和服务器进行正在连接的过程(正在三次握手)般填5,测试得知,ARM最大为8
注:调用listen()把主动套接字变成被动套接字
accept():阻塞等待客户端连接请求
#include <sys/types .h>
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr,socklen t *addrlen);
参数:
sockfd:经过前面socket()创建并通过bind()、listen()设置过的fd;
struct sockaddr,socklen t *addrlen同bind()
其中struct sockaddr 用于存储客户端发来的客户端信息。
成功时返回已经建立好连接的新的newfd,后续做读写操作是针对newfd 进行数据读写。
服务器端例程:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#define SERV_PORT 6000
#define BUF_SIZE 256
int main(void) {
int sockfd;
int clientfd;
socklen_t length = sizeof(struct sockaddr_in);
char buf[BUF_SIZE]={0};
struct sockaddr_in addr;
struct sockaddr_in client_addr;
bzero(&addr, sizeof(struct sockaddr_in));
bzero(&client_addr,sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(SERV_PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0){
printf("create sockfd failed \n");
return -1;
}
else{
printf("sockfd = %d \n",sockfd);
}
int mw_optval = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&mw_optval,sizeof(mw_optval));
if(bind(sockfd, (struct sockaddr*)(&addr),sizeof(struct sockaddr))<0){
printf("bind port failed \n");
}
if(listen(sockfd, 5)<0){
printf("listen fail \n");
}
clientfd = accept(sockfd,(struct sockaddr*)(&client_addr),&length);
if(clientfd<0){
printf("accept fail \n");
}
while(1){
recv(clientfd,buf,sizeof(buf),0);
printf("buf = %s\n",buf);
bzero(buf,sizeof(buf));
strcpy(buf,"hello world");
send(clientfd,buf,sizeof(buf),0);
bzero(buf,sizeof(buf));
sleep(1);
}
return EXIT_SUCCESS;
}
补充一:
linux socket 程序被ctrl+c或者异常终止,再次起程序时提示该端口号已被绑定bind err:: Address already in use
原因:
在服务端终止之后,会有一个TIME_WAIT的状态,持续2-4分钟,再次打开会出现绑定bind失败
解决方法:
在bind函数前增加以下代码:
int mw_optval = 1;
setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&mw_optval,sizeof(mw_optval));
服务器端可以尽可能的使用SO_REUSEADDR(在绑定之前尽可能调用setsockopt来设置SO_REUSEADDR)套接字选项,这样就可以使得不必等待TIME_WAIT状态就可以重启服务器了,也就是说:TIME_WAIT状态还是存在的,但是不影响我们重新启动服务器。
参考:https://blog.csdn.net/z5z5z5z56/article/details/107834143
补充二:
设置调用close(socket)后,仍可继续重用该socket。调用close(socket)一般不会立即关闭socket,而经历TIME_WAIT的过程。
BOOL bReuseaddr = TRUE;
setsockopt( s, SOL_SOCKET, SO_REUSEADDR, ( const char* )&bReuseaddr, sizeof( BOOL ) );
如果要已经处于连接状态的soket在调用closesocket()后强制关闭,不经历TIME_WAIT的过程:
BOOL bDontLinger = FALSE;
setsockopt( s, SOL_SOCKET, SO_DONTLINGER, ( const char* )&bDontLinger, sizeof( BOOL ) );
参考:https://blog.csdn.net/Dontla/article/details/123737704