TCP客户/服务器模型
socket
socket函数
包含头文件<sys/socket.h>
功能:创建一个套接字用于通信
原型
int socket(int domain, int type, int protocol);
参数
domain :指定通信协议族(protocol family)
type:指定socket类型,流式套接字SOCK_STREAM,数据报套接字SOCK_DGRAM,原始套接字SOCK_RAW
protocol :协议类型
返回值:成功返回非负整数, 它与文件描述符类似,我们把它称为套接口描述字,简称套接字。失败返回-1
bind
bind函数
包含头文件<sys/socket.h>
功能:绑定一个本地地址到套接字
原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
sockfd:socket函数返回的套接字
addr:要绑定的地址
addrlen:地址长度
返回值:成功返回0,失败返回-1
listen
listen函数
一般来说,listen函数应该在调用socket和bind函数之后,调用函数accept之前调用。
对于给定的监听套接口,内核要维护两个队列:
1、已由客户发出并到达服务器,服务器正在等待完成相应的TCP三路握手过程
2、已完成连接的队列
accept
accept函数
包含头文件<sys/socket.h>
功能:从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。
原型
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数
sockfd:服务器套接字
addr:将返回对等方的套接字地址
addrlen:返回对等方的套接字地址长度
返回值:成功返回非负整数,失败返回-1
connect
connect函数
包含头文件<sys/socket.h>
功能:建立一个连接至addr所指定的套接字
原型
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
sockfd:未连接套接字
addr:要连接的套接字地址
addrlen:第二个参数addr长度
返回值:成功返回0,失败返回-1
Socket API 中的地址复用
SO_REUSEADDR
服务器端尽可能使用SO_REUSEADDR
在绑定之前尽可能调用setsockopt来设置SO_REUSEADDR套接字选项。
使用SO_REUSEADDR选项可以使得不必等待TIME_WAIT状态消失就可以重启服务器
2client.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
/*
IPV4国际套接字地址
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
};
长连接与短连接客户端说的算!
*/
//创建一个TCP协议的客户端
//长连接
#if 0
void test()
{
int sockfd = 0;
const char *serverip = "192.168.66.128";
//创建socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket()");
exit(0);
}
//定义socket结构体 man 7 ip
struct sockaddr_in srvsddr;
srvsddr.sin_family = AF_INET;
srvsddr.sin_port = htons(8001);//转化为网络字节序
//第一种
#if 0
srvsddr.sin_addr.s_addr = inet_addr(serverip);
#endif
//第二种
#if 0
//srvsddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 就是0.0.0.0 不存在网络字节序
//srvaddr.sin_addr.s_addr = inet_addr(INADDR_ANY); //绑定本机的任意一个地址
#endif
//第三种
//建议使用这种
#if 1
int ret;
ret = inet_pton(AF_INET, serverip, &srvsddr.sin_addr);
if (ret == 0)
{
printf("%s is invalid\n", serverip);
return;
}
#endif
//进程-》内核
if( connect(sockfd, (struct sockaddr *)&srvsddr,sizeof(srvsddr)) < 0)
{
perror("connect()");
exit(0);
}
char recvbuf[1024] = {0};
char sendbuf[1024] = {0};
//char *fgets(char *s, int size, FILE *stream); 从stream 读取size-1大小的数据存入s,最后加'\0'
while( fgets(sendbuf, sizeof(sendbuf), stdin) != NULL )
{
//向服务器写数据
//ssize_t write(int fd, const void *buf, size_t count);
// 从buf中读取count大小的数据存入文件描述符为fd的文件中。
write(sockfd, sendbuf, strlen(sendbuf));
//ssize_t read(int fd, void *buf, size_t count);
//从文件描述符为fd的文件中读取大小为count的数据存入buf中。
read(sockfd, recvbuf, sizeof(recvbuf));
fputs(recvbuf, stdout);//从服务器收到数据,打印屏幕
//清空缓冲区
memset(recvbuf, 0, sizeof(recvbuf));
memset(sendbuf, 0, sizeof(sendbuf));
}
close(sockfd);
return;
}
#endif
//短连接
#if 1
void test()
{
int i = 0;
//创建多个socket发送信息
for (i = 0; i < 10; i++)
{
int sockfd = 0;
sockfd = socket(PF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("fun socket\n");
exit(0);
}
struct sockaddr_in srvaddr;
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(8001);
srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //127.0.0.1
//srvaddr.sin_addr.s_addr = inet_addr(INADDR_ANY); //绑定本机的任意一个地址
if (connect(sockfd, (struct sockaddr*) (&srvaddr), sizeof(srvaddr)) < 0)
{
printf("errno:%d \n", errno);
perror("fun socket\n");
exit(0);
}
char revbuf[1024] = { 0 };
char sendbuf[1024] = { 0 };
sprintf(sendbuf, "i:%d\n", i);
//向服务写数据
write(sockfd, sendbuf, strlen(sendbuf)); //服务器端回发信息
//从服务器读数据
read(sockfd, revbuf, sizeof(revbuf));
fputs(revbuf, stdout); //从服务器收到数据,打印屏幕
memset(revbuf, 0, sizeof(revbuf));
memset(sendbuf, 0, sizeof(sendbuf));
close(sockfd);
}
}
#endif
int main()
{
test();
return 0;
}
2server.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
/*
IPV4国际套接字地址
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
};
*/
//创建一个TCP协议的服务器
#if 1
void test()
{
int sockfd = 0;
const char *serverip = "192.168.66.128";
//创建socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket()");
exit(0);
}
//定义socket结构体 man 7 ip
struct sockaddr_in srvsddr;
srvsddr.sin_family = AF_INET;
srvsddr.sin_port = htons(8001);//转化为网络字节序
//第一种
#if 0
srvsddr.sin_addr.s_addr = inet_addr(serverip);
#endif
//第二种
#if 0
//srvsddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 就是0.0.0.0 不存在网络字节序
//srvaddr.sin_addr.s_addr = inet_addr(INADDR_ANY); //绑定本机的任意一个地址
#endif
//第三种
//建议使用这种
#if 1
int ret;
ret = inet_pton(AF_INET, serverip, &srvsddr.sin_addr);
if (ret == 0)
{
printf("%s is invalid\n", serverip);
return;
}
#endif
//设置端口复用
//使用SO_REUSEADDR选项可以使得不必等待TIME_WAIT状态消失就可以重启服务器
int optval = 1;
if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
{
perror("getsockopt()");
exit(0);
}
//从进程->内核 将结构首地址和该结构大小都传递给了内核
//内核知道需要从进程复制多少数据进来
if(bind(sockfd, (struct sockaddr *)&srvsddr,sizeof(srvsddr)) <0 )
{
perror("bind()");
exit(0);
}
//一但调用listen函数,这个套接字sockfd将变成被动套接字,只能接受连接,
//不能主动的发送连接
//listen 做了两个队列。。。。。。
// 队列由内核管理,一部分是完成三次握手的,一部分是没有完成三次握手的。
if(listen(sockfd, SOMAXCONN) < 0)
{
perror("listen()");
exit(0);
}
/*
值-结果参数:传入结构体的首地址和结构体的长度,这样内核
在写该结构不至于越界;当函数返回时,结构大小又是一个结果
*/
struct sockaddr_in peeraddr;
unsigned int conn = 0;
socklen_t peerlen = sizeof(peeraddr);//值-结果参数
//内核到进程
//accept接受已经完成三次握手的链接,没有链接会阻塞直到有链接
//sockfd监听套接字 conn连接套接字
conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen);
if (-1 == conn)
{
perror("accept()");
exit(0);
}
printf("传入后 peerlen = %d\n", peerlen);
#if 1
printf("客户端的ip:%s port:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
#endif
#if 1
char ip[16] = {0};
socklen_t size = sizeof(ip);
if (ip != NULL)
{
//inet_ntop(int af, const void *src,char *dst, socklen_t size)
inet_ntop(AF_INET, (void *)&peeraddr.sin_addr.s_addr, (char *)ip, size);
}
if (errno == ENOSPC)
{
perror("分配的内存size不够");
return;
}
printf("客户端的ip:%s port:%d\n", ip, ntohs(peeraddr.sin_port));
#endif
char recvbuf[1024] ={0};
while(1)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = read(conn, recvbuf, sizeof(recvbuf));
if (ret == 0)
{
printf("对方已经关闭\n");
exit(0);
}
else if (ret < 0)
{
perror("读取数据失败\n");
exit(0);
}
fputs(recvbuf, stdout);//服务器收到数据,打印到屏幕
write(conn, recvbuf, ret); //将收到的数据再发给客户端
}
return;
}
#endif
int main()
{
test();
return 0;
}