1 TCP通信原理

2 API接口函数
2.1 建立套接字接口
#include<sys/socket.h>
int socket(int family, int type, int protocol);
- family:目前支持5种协议簇,最常用的有AF_INET(IPv4协议)和AF_INET6(IPv6协议)、AF_LOCAL(或称AF_UNIX,Unix域socket),AF_ROUTE等等。协议簇决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用IPv4地址(32位)与端口(16位)的组合,AF_UNIX决定了要用一个绝对路径名作为地址。
- type:指明套接口类型,共有三种类型可选
SOCK_STREAM(字节流套接口)
SOCK_DGRAM(数据报套接口)
SOCK_RAW(原始套接口) - protocol:顾名思意,就是指定协议。常用的协议有IPPRPTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等。当protocol为0的时候,会自动选择type类型对应的默认协议。
- return:若是成功则返回非负描述字,失败返回-1
2.2 为套接口分配一个本地IP和协议端口
#include<sys/socket.h>
int bind(int sockfd,
const struct sockaddr* server,
socklen_t addrlen);
- sockfd:socket函数返回的描述字。
- server:指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同。
2.2.1 IPv4
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};
struct in_addr {
uint32_t s_addr;
};
2.2.2 IPv6
struct sockaddr_in6 {
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
struct in6_addr {
unsigned char s6_addr[16];
};
2.2.3 Unix
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family;
char sun_path[UNIX_PATH_MAX];
};
2.3 等待客户端连接请求
#include<sys/socket.h>
int listen(int sockfd, int backlog);
- sockfd:socket函数返回的套接口描述字。
- backlog:规定了内核为此套接口排队的最大连接个数。
- return:0表示成功,-1表示失败
2.4 连接客户端
#include<sys/socket.h>
int accept(int listenfd,
struct sockaddr *client,
socklen_t *addrlen);
- listenfd:socket函数返回的套接口描述字。
- client:客户端的套接口结构体地址。
- addrlen:客户端的套接口结构体的长度。
- return:该函数返回的是一个全新的套接口描述字符。
- note:如果对客户端的信息不感兴趣,可以将第二和第三参数置空。
2.5 连接服务器
#include<sys/socket.h>
int connect(int sockfd,
const struct sockaddr *addr,
socklen_t addrlen)
- sockfd:套接口描述字
- addr:sockaddr: 结构体指针
- addrlen:第二个参数结构体的大小
3 读写数据
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
- brief:可以读取文件,指某一个已打开的文件中,读取一定量的字符,然后将这些读取字符,放入某一个预存的缓冲区内,供以后使用。
- fd:文件描述符
- buf:指缓冲区,即读取的数据会被放入到这个缓冲区中去。
- count:传送count个字节到buf指针所指的内存中
- return:返回值为实际读取到的字节数。如果返回值是0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。如果返回值-1,表明读取文件失败。
- Note:读常规文件是不会阻塞的,如果读字符终端、网络的socket描述字,管道文件等,这些文件的缺省read都是阻塞的方式。
4 服务器端代码-Linux
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
void
ProcessConnect(int new_sock){
while(1){
char buf[1024] = {0};
ssize_t read_size = read(new_sock, buf, sizeof(buf) - 1);
printf("read_size = %ld\n", read_size);
if(read_size < 0){
perror("read");
continue;
}
if(read_size == 0){
printf("[client %d] disconnect!\n", new_sock);
close(new_sock);
return;
}
buf[read_size] = '\0';
printf("[client %d] %s\n", new_sock, buf);
write(new_sock, buf, strlen(buf));
}
}
int
main(int argc, char *argv[]){
if(argc != 3){
printf("Uage ./server [ip] [port]\n");
return 1;
}
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sock < 0){
perror("socket");
return 1;
}
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(argv[1]);
server.sin_port = htons(atoi(argv[2]));
int ret = bind(listen_sock, (sockaddr *)&server, sizeof(server));
if(ret < 0){
perror("bind");
return 1;
}
ret = listen(listen_sock, 5);
if(ret < 0){
perror("listen");
return 1;
}
printf("Server Init OK!\n");
while(1){
sockaddr_in peer;
socklen_t len = sizeof(peer);
int new_sock = accept(listen_sock, (sockaddr *)&peer, &len);
printf("Client Addr:%s,Port:%d Linked******\n", inet_ntoa(peer.sin_addr), peer.sin_port);
if(new_sock < 0){
perror("accept");
continue;
}
printf("[client %d]connect!\n", new_sock);
ProcessConnect(new_sock);
}
return 0;
}
参考1:TCP服务器和客户端程序设计
参考2:代码参考