五、TCP
一二章请前往:网络编程_1(网络基础+跨主机传输
三四章请前往:网络编程_2(网络属性+UDP(UDP模型+广播组播))
(一)TCP模型
1.TCP模型
2.listen
功能:监听套接字;
头文件:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
原型:
int listen(int sockfd, int backlog);
参数:
int sockfd:用socket函数生成的流式套接字.要被转换成监听套接字的套接字。
int backlog:允许建立多少链接。
内核会为每一个socket套接字维护两个队列.
未完成连接的队列。/proc/sys/net/ipv4/tcp_max_syn_backlogc
以完成连接的队列,等着accept函数拿走;
返回值:
成功,返回0;
失败,返回-1;
3.accept(阻塞函数)
功能:接收客户端的链接,并返回套接字;从已完成链接的队列头中,取出一个链接。(文件描述符);
头文件:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
int sockfd:已经被listen监听的套接字;
struct sockaddr *addr:存储链接成功的客户端的信息:ip和端口。
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
socklen_t *addrlen:第二个结构体的大小,就是sizeof(struct sockaddr_in);
参数:
成功,返回一个新的套接字,是与客户端建立链接的套接字;
往这个套接字中读写消息,就是与客户端通讯;
失败,返回-1,更新errno。
4.recv
功能:从套接字中获取数据;
头文件:
#include <sys/types.h>
#include <sys/socket.h>
原型:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t read(int fd, void *buf, size_t count);
参数:
int sockfd:从该文件描述符中读取数据; accept函数返回的新的套接字;
void *buf:存储获取到的数据;
size_t len:指定要接受的数据大小;
int flags:接收方式:0,默认方式接收。阻塞;
返回值:
> 0;接收的字符个数;
=0; 对方关闭,对方调用close关闭文件描述符;
=-1;函数调用出错.
errno = EINTR The receive was interrupted by delivery of a signal before any data werec available;
需要忽略犹豫信号干扰造成的数据传输中断。
5.send
功能:发送数据到套接字中;
头文件:
#include <sys/types.h>
#include <sys/socket.h>
原型:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t write(int fd, const void *buf, size_t count);
参数:
int sockfd:套接字;
void *buf:需要发送的数据;
size_t len:需要发送的数据大小;
int flags:发送方式:0,默认方式发送。阻塞;
返回值:
成功,返回发送成功的数据个数;
失败,返回-1,更新errno;
errno = EINTR The receive was interrupted by delivery of a signal before any data were available;
需要忽略犹豫信号干扰造成的数据传输中断。
6.connect
功能:连接服务器;
头文件:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
原型:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
int sockfd:套接字;
struct sockaddr *addr:通用结构体,iPv4的结构体,填充服务器的地址信息和端口;
socklen_t addrlen:第二个结构体的大小;
返回值:
成功,返回0;
失败,返回-1,跟新errno;
7.shutdown
#include <sys/socket.h>
int shutdown(int sockfd,int how)
参数:
SHUT_RD关闭读端,无法从套接字种读取数据
SHUT_WR关闭写端,无法使用套接字发送数据
SHUT_RDWR关闭读写端,则无法读写
返回值:
成功返回0
失败返回-1,设置errno
(二)服务器模型
1.循环服务器
1)一次只能处理一个客户端,等这个客户端退出后,才能处理下一个客户端。
2)缺点循环服务器所处理的客户端不能有耗时操作
socket();
bind();
listen();
while(1){
newfd = accept();
while(1){
recv();
fun`````
}
close();
}
close();
例子
服务器
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
//1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd < 0)
{
perror("socket");
return -1;
}
//允许本地端口快速重用
int reuse = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
//2.绑定
//填充服务器信息
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(2020);
sin.sin_addr.s_addr = inet_addr("0");
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin))<0)
{
perror("bind");
exit(1);
}
printf("绑定成功\n");
//3.监听
if(listen(sfd, 3)<0)
{
perror("listen");
exit(1);
}
//4.接收客户端的连接
struct sockaddr_in cin;
socklen_t len = sizeof(cin);
int cntfd = -1;
while(1)
{
cntfd = accept(sfd, (void*)&cin, &len);
if(cntfd < 0)
{
perror("accept");
exit(1);
}
printf("客户端链接成功 %d\n", cntfd);
char ip[20] = "";
inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
printf("[%s:%d]连接成功\n", ip, ntohs(cin.sin_port));
int ret = -1;
while(1)
{
//5.recv接收数据
char buf[256] = "";
bzero(buf, sizeof(buf));
do
{
ret= recv(cntfd, buf, 256, 0);
}while(ret<0 && errno == EINTR);
if(ret < 0)
{
perror("recv");
exit(1);
}
else if(0 == ret)
{
fprintf(stderr, "对方关闭\n");
break;
}
printf("%s\n", buf);
}
close(cntfd);
}
close(sfd);c
return 0;
}
客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
//1.创建套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
{
perror("socket");
return -1;
}
//允许本地端口快速重用
int reuse = 1;
int len = sizeof(reuse);
if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, len)<0)
{
perror("setsockopt");
exit(1);
}
//2.绑定 非必须
//3.连接服务器
//填充服务器信息
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(2020);
sin.sin_addr.s_addr = inet_addr("192.168.1.141");
//int connect(int sockfd, \
//const struct sockaddr *addr, socklen_t addrlen);
if(connect(fd, (void*)&sin, sizeof(sin))<0)
{
perror("connect");
exit(1);
}
printf("连接成功\n");
char buf[256] = "";
//4.发送消息
int ret = -1;
while(1)
{
bzero(buf, sizeof(buf));
fprintf(stderr, "请输入:");
fgets(buf, 256-1, stdin);
// ssize_t send(int sockfd, const void *buf, size_t len, int flags);
do
{
ret = send(fd, buf, strlen(buf), 0);
}while(ret<0 && errno == EINTR);
if(ret < 0)
{
perror("send");
exit(1);
}
}
close(fd);
return 0;
}
2.并发服务器
1)可以同时处理多个客户端请求,创建子进程或者线程来处理客户端的请求
2)父进程/主线程只负责连接,子进程/分支线程 只负责与客户端进行交互。
1.多进程并发服务器
void handler(int signo) {
waitpid()
}
sockfd = socket();
bind();
listen();
signal(SIGCHLD,handler);
while(1){
confd=accpet();
/*******子进程*******/
if(fork()==0){
close(sockfd);
while(1){
recv();
fun------;c
}
exit(1);
}
/*******父进程*******/
close(confd);
}
close(scokfd);
代码实现
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
void show_info(struct sockaddr_in cin);
void recv_cli_msg(int newfd, struct sockaddr_in cin);
void handler(int sig)
{
//信号处理函数中不能有耗时操作
if (signo == SIGCHLD) {
while(waitpid(-1,NULL,WNOHANG) > 0);
//循环回收多个子进程,
//当waitpid非阻塞成功回收子进程后,
//继续尝试回收一次,如果回收成功,继续尝试回收
//如果回收失败,说明没有僵尸进程需要回收了
}
int main(int argc, const char *argv[])
{
//捕获信号
if(signal(SIGCHLD, handler) == SIG_ERR)
{
perror("signal");
return -1;
}
//1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd < 0)
{
perror("socket");
return -1;
}
//允许本地端口快速重用
int reuse = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
//2.绑定
//填充服务器信息
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(2020);
sin.sin_addr.s_addr = inet_addr("0");
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin))<0)
{
perror("bind");
exit(1);
}
printf("绑定成功\n");
//3.监听
if(listen(sfd, 3)<0)
{
perror("listen");
exit(1);
}
//4.接收客户端的连接
struct sockaddr_in cin;
socklen_t len = sizeof(cin);
int cntfd = -1;
while(1)
{
cntfd = accept(sfd, (void*)&cin, &len);
if(cntfd < 0)
{
perror("accept");
exit(1);
}
printf("客户端链接成功 %d\n", cntfd);
show_info(cin);
int ret = -1;
pid_t pid = fork();
if(0 == pid)
{
//子进程 与客户端进行数据交互;
close(sfd);
recv_cli_msg(cntfd, cin);
close(cntfd);
exit(1);
}
else if(pid > 0)
{
//父进程直接返回。
close(cntfd);
}
else
{
perror("fork");
close(cntfd);
}
}
close(sfd);
return 0;
}
void show_info(struct sockaddr_in cin)
{
char ip[20] = "";
inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
printf("[%s:%d]连接成功\n", ip, ntohs(cin.sin_port));
}
void recv_cli_msg(int newfd, struct sockaddr_in cin)
{
char buf[256] = "";
int ret = -1;
char ip[20] = "";
inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
int port = ntohs(cin.sin_port);
while(1)
{
bzero(buf, sizeof(buf));
//接收数据
do
{
ret = recv(newfd, buf, 256, 0);
}while(ret < 0 && EINTR == errno);
if(ret < 0)
{
perror("recv");
return;
}
else if(0 == ret)
{
fprintf(stderr, "[%s:%d] 客户端断开连接\n",ip, port);
return;
}
printf("[%s:%d]:%s\n", ip, port, buf);
}
}
2.多线程并发服务器
sockfd = socket();
bind();
listen();
while(1){
confd=accpet();
/****子线程********/c
pthread_create() --> func接收消息
/****子线程********/
pthrad_detach()
}
close(scokfd);
代码实现
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <pthread.h>
typedef struct
{
int newfd;
struct sockaddr_in cin;
}__cli_info;
void show_info(struct sockaddr_in cin);
void* recv_cli_msg(void* arg);
int main(int argc, const char *argv[])
{
//1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd < 0)
{
perror("socket");
return -1;
}
//允许本地端口快速重用
int reuse = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
//2.绑定
//填充服务器信息
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(2020);
sin.sin_addr.s_addr = inet_addr("0");
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin))<0)
{
perror("bind");
exit(1);
}
printf("绑定成功\n");
//3.监听
if(listen(sfd, 3)<0)
{
perror("listen");
exit(1);
}
printf("服务器启动成功\n");
//4.接收客户端的连接
struct sockaddr_in cin;
socklen_t len = sizeof(cin);
int cntfd = -1;
while(1)
{
cntfd = accept(sfd, (void*)&cin, &len);
if(cntfd < 0)
{
perror("accept");
exit(1);
}
printf("客户端链接成功 %d\n", cntfd);
show_info(cin);
int ret = -1;
//创建一个线程
__cli_info cliInfo;
cliInfo.newfd = cntfd;
cliInfo.cin = cin;
//有一个客户端一旦连接成功,就创建一个线程,作为与客户到交互的线程。
pthread_t tid = -1;
if(pthread_create(&tid, NULL, recv_cli_msg, (void*)&cliInfo) < 0)
{
perror("pthread_create");
close(cntfd);
}
}
close(sfd);
return 0;
}
void show_info(struct sockaddr_in cin)
{
char ip[20] = "";
inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
printf("[%s:%d]连接成功\n", ip, ntohs(cin.sin_port));
}
void* recv_cli_msg(void* arg)
{
//分离线程,线程退出后自动回收资源;
pthread_detach(pthread_self());
__cli_info cli = *(__cli_info*)arg;
int newfd = cli.newfd;
struct sockaddr_in cin = cli.cin;
char ip[20] = "";
inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
int port = ntohs(cin.sin_port);
char buf[256] = "";
int ret = -1;
while(1)
{
bzero(buf, sizeof(buf));
//接收数据
do
{
ret = recv(newfd, buf, 256, 0);
}while(ret < 0 && EINTR == errno);
if(ret < 0)
{
perror("recv");
break;
}
else if(0 == ret)
{
fprintf(stderr, "[%s:%d]断开连接\n", ip, port);
break;
}
printf("[%s:%d]:%s\n", ip,port, buf);
}
close(newfd);
pthread_exit(NULL);
}