TCP进行通信的过程(三次握手):
- 连接的发起段(通常称为客户端),向目标计算机(通常称为服务器)发送一个请求建立连接的数据包。
- 服务器收到请求后,对客户端的同步信号做出响应、发送自己的同步信号给客户端。
- 客户端对服务器端发来的同步信号进行响应。至此,连接建立完成,就可以进行数据传输了。
TCP连接的关闭:
- 请求主机发送一个关闭连接的请求给另一方。
- 另一方收到关闭连接的请求后,发送一个接收请求的确认数据包,并关闭它的socket连接。
- 请求主机收到确认数据包后,发送一个确认数据包,告知另一方其发送的确认信息已收到,同时请求主机关闭它的socket连接。
套接字地址结构
结构struct sockaddr_in定义了TCP/IP协议族的套接字地址格式,定义如下:
#include <netinet/in.h>
struct sockaddr_in{
unsigned short sin_family; //地址类型
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //IP地址
unsigned char sin_zero[8]; //填充字节,一般赋值为0
};
sin_family表示地址类型,对于使用TCP/IP协议进行的网络编程,该值只能是AF_INET。
sin_addr用来存储32位的IP地址,定义如下:
struct in_addr{
unsigned long s_addr;
};
设置地址信息的示例代码:
......
struct sockaddr_in sock;
sock.sin_family=AF_INET;
sock.sin_port=htons(80); //设置端口
sock.sin_addr.s_addr=inet_addr("202.205.3.195"); //设置IP地址
memset(sock.sin_zero,0,sizeof(sock.sin_zero)); //将数组sin_zero设置为0
创建socket套接字
通过调用函数socket创建套接字。以下示例代码创建了一个TCP套接字:
int sock_fd;
sock_fd=socket(AF_INET,SOCK_STREAM,0); //SOCK_STREAM代表创建TCP套接字
if(sock_fd < 0){
perror("socket error\n");
exit(1);
}
创建UDP协议的套接字为:
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
建立连接
connect函数的原型:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
函数connect用来在一个指定的套接字上创建一个连接,服务器的IP地址和端口号由参数serv_addr指定。通常一个面向连接的套接字(TCP套接字)只能调用一次connect函数,用于向服务器发出连接请求。而对于无连接的套接字(UDP套接字),connect函数并不建立真正的连接,它只是告诉内核与该套接字进行通信的目的地址,只有该目的地址发来的数据才会被socket接收。所以UDP套接字可以多次调用connect函数来改变与目的地址的绑定。
connect函数的用法示例:
......
if(connect(sock_fd,(struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in)) <0){
perror("connect error\n");
exit(1);
}
注意:须将serv_addr强制转换为struct sockaddr类型。
绑定套接字
函数bind用来将一个套接字和某个端口绑定的一起。原型:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
socket函数只是创建了一个套接字,它将工作在哪个端口上,程序尚未指明。因此需要用bind函数将一个套接字和某个端口绑定在一起。
参数my_addr指定了sockfd将绑定到的本地地址,可以将my_addr的sin_addr设置为INADDR_ANY而不是某个确定的IP地址就可以绑定到任何网络接口。对于只有一个IP地址的计算机,对应的就是它的IP地址;对于有多块网卡的计算机,INADDR_ANY表示将处理来自所有网络接口上相应端口的连接请求。
bind函数的典型用法:
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(80);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sock_fd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in)) <0){
perror("bind error\n");
exit(1);
}
监听套接字
调用函数listen对套接字进行监听。
#include <sys/socket.h>
int listen(int s, int backlog);
由函数socket创建的套接字是主动套接字,可以用来主动请求连接到某个服务器(通过connect函数)。但是作为服务器端的程序,通常在某个端口上监听等待来自客户端的连接请求。在服务器端,一般是先调用函数socket创建一个主动套接字,然后调用函数bind将该套接字绑定到某个端口上,接着再调用函数listen将该套接字转化为监听套接字,等待来自客户端的连接请求。
该函数典型用法:
#define LISTEN_NUM 12 //定义连接请求队列长度
......
if(listen(sock_fd,LISTEN_NUM) <0){
perror("listen error\n");
exit(1);
}
接收连接
用accept函数来接受一个连接请求。
#include <sys/types.h>
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
- 参数s是由socket创建、经函数bind绑定到本地某一端口上,然后通过函数listen转化而来的监听套接字。
- 参数addr用以保存发起连接请求的主机的地址和端口。
- 参数addrlen是addr所指向的结构体的大小。
只能对面向连接的套接字使用accept函数,此函数执行成功返回一个新的代表客户端的套接字,出错返回-1。进程可以利用这个新的套接字描述符与客户端交换数据。参数s所指定的套接字继续等待客户端的连接请求。
参数s所指定的套接字默认被设置为阻塞方式,accept()函数将被阻塞直到有连接请求到达为止。常见用法:
int client_fd;
int client_len;
struct sockaddr_in client_addr;
......
client_len=sizeof(struct sockaddr_in);
client_fd=accept(sock_fd, (struct sockaddr *)&client_addr, &client_len);
if(conn_fd < 0){
perror("accept");
exit(1);
}
TCP套接字发送数据
send()用来在TCP套接字上发送数据。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int s, const void *msg, size_t len, int flags);
- 函数send只能对处于连接状态的套接字使用。
- 参数s为已建立好连接的套接字描述符,即accept函数的返回值。
- 参数msg指向存放待发送数据的缓冲区。
- 参数len为待发数据的长度。
- 参数flags为控制选项,一般设置为0。
执行成功返回实际发送数据的字节数,出错返回-1。注意,执行成功只是说明数据写入套接字的缓冲区,并不表示数据已经成功地通过网络发送到目的地。
send函数的常见用法:
#define BUFFERSIZE 1500
char send_buf[BUFFERSIZE];
......
if(send(conn_fd, send_buf, len, 0) < 0){
perror("send error\n");
exit(1);
}
接收数据
函数recv用来在TCP套接字上接收数据。其原型:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int s, void *buf, size_t len, int flags);
recv从参数s所指定的套接字描述符(必须是面向连接的套接字)上接收数据并保存到参数buf所指定的缓冲区,参数len为缓冲区长度。参数flags为控制选项,一般设置为0。如果在指定的套接字上无数据到达时,recv()将被阻塞。
执行成功返回接收到的数据字节数,出错则返回-1。
该函数常见用法:
char recv_buf[BUFFERSIZE];
......
if(recv(conn_fd, recv_buf, sizeof(recv_buf), 0) < 0){
perror("recv");
exit(1);
}
UDP套接字发送数据
函数sendto用来在UDP套接字上发送数据。原型:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
函数sendto与函数send类似,但它不需要套接字处于连接状态。因为是无连接的套接字,所以在使用时需要制定数据的目的地址。
- 参数msg指向待发送数据的缓存区
- 参数len指定了待发数据的长度
- 参数flags控制选项,一般设置为0
- 参数to用于指定目的地址
- 参数tolen指定目的地址的长度
UDP套接字接收数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
函数recvfrom与函数recv功能类似,只是recv只能用于面向连接的套接字。
- 参数buf指向接收缓冲区
- 参数len指定了缓冲区的大小
- 参数flags控制选项,一般设置为0
- 参数from中保存数据的源地址
- 参数fromlen在调用recvfrom前为参数from的长度,调用recvfrom后将保存from的实际大小
关闭套接字
函数close用来关闭一个套接字描述符,它与关闭文件描述符是类似的。
#include <unistd.h>
int close(int fd);
- 参数fd为一个套接字描述符,该函数关闭一个套接字。