1、TCP编程模型:
服务器端:socket(创建套接字)–>bind(绑定套接字)–>listen(设计套接字监听数量)–>accept(等待客服端访问)–>read/write(对客服端进行读写操作)–>close(关闭套接字)
客服端: socket(创建套接字)—>connect(连接服务器)–>-->read/write(对服务器进行读写)–>close(关闭套接字)
2、socket函数原型:
int socket(int domain,int type, int protocol)
domain:指明了协议族/域,通常AF_INET、AF_INET6、AF_LOCAL等;
type:是套接口类型,主要SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、SOCK_RAW(原始socket);
protocol:一般取为0。成功时,返回一个小的非负整数值,与文件描述符类似
返回值:非负描述符 – 成功,-1 - 出错
3、bind函数原型:
int bind(int sockfd, const struct sockaddr * my_addr, socklen_t addrlen);
函数功能:把一个本地协议地址赋予一个套接字。对于网际协议,协议地址是32位的IPv4地址或是128位的IPv6地址与16位的TCP或UDP端口号的组合。 对于IPv4来说,通配地址通常由INADDR_ANY来指定,其值一般为0。它告知内核去选择IP地址。
sockfd: 套接字编号
my_addr: 是一个指向sockaddr结构体类型的指针
addrlen: 表示my_addr结构的长度,可以用sizeof操作符获得。
返回值: 返回值:成功返回0,失败返回-1
4、listen函数原型:
int listen(int sockfd, int backlog);
函数功能:主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
sockfd :一个已绑定未被连接的套接字描述符
backlog: 连接请求队列(queue of pending connections)的最大长度(一般由2到4)。用SOMAXCONN则由系统确定。
5、accept函数原型:
SOCKET accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
函数功能:accept()是在一个套接口接受的一个连接。本函数从s的等待连接队列中抽取第一个连接,创建一个与s同类的新的套接口并返回句柄。
sockfd:套接字描述符,该套接口在listen()后监听连接。
addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
addrlen:(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。
返回值: socket编号
注意:对应两个队列:一个是已完成的连接队列;另一个是未完成的连接队列。最大数指的就是已完成的连接队列最大数。
6、connect函数原型:
int connect(int soctfd, const struct sockaddr * addr, int addrlen);
函数功能: 用于建立与指定socket的连接。
soctfd:标识一个未连接socket
addr:指向要连接套接字的sockaddr结构体的指针
addrlen:sockaddr结构体的字节长度
返回值: 成功0;失败-1
a. 硬错:端口号错误,服务器进程未开启,收到RST,立刻返回ECONNREFUSED;
b. 软错:IP不可达,协议ICMP,比如no route to host,通常是发送arp请求无响应。
7、端口复用
在网络通信中,一个端口只能被一个进程使用,不能多个进程共用同一个端口。我们在进行套接字通信的时候,如果按顺序执行如下操作:先启动服务器程序,再启动客户端程序,然后关闭服务器进程,再退出客户端进程,最后再启动服务器进程,就会出如下的错误提示信息:bind error: Address already in use
举例端口port为:10000。 server为服务端程序编译后的可执行文件,当执行出现“bind error: Address already in use”,使用shell指令查看TCP状态,如下所示:
# 第二次启动服务器进程
$ ./server
bind error: Address already in use
$ netstat -apn|grep 10000
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 127.0.0.1:10000 127.0.0.1:50178 TIME_WAIT
通过 netstat 查看 TCP 状态,发现上一个服务器进程其实还没有真正退出。因为服务器进程是主动断开连接的进程,最后状态变成了 TIME_WAIT 状态,这个进程会等待 2msl(大约1分钟) 才会退出,如果该进程不退出,其绑定的端口就不会释放,再次启动新的进程还是使用这个未释放的端口,端口被重复使用,就是提示 bind error: Address already in use 这个错误信息。
如果想要解决上述问题,需使用setsockopt函数设置端口复用,该函数使用在服务端,在bind函数前使用。使用的函数原型如下:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
函数功能:设置套接字选项
sockfd:用于监听的文件描述符
level:设置端口复用需要使用 SOL_SOCKET 宏
optname:要设置什么属性(SO_REUSEADDR/SO_REUSEPORT这两个宏都可设置端口复用)
optval:设置是去除端口复用属性还是设置端口复用属性,使用 int 型变量设置(0:不设置 ;1:设置)
optlen:optval 指针指向的内存大小 sizeof (int)
返回值:成功:0;失败:-1
8、socket头文件:
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/poll.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
9、客服端多线程读写例子:
#define BUF_SIZE 100
#define SERVER_IP "192.168.117.128" //IP号之间不能有空格
int socketfd = 0;
void FuncExit(int signal)
{
printf("socketC exit!\n");
close(socketfd); //关闭服务器
exit(0);
}
void *func(void *arg)
{
int byte;
char *buf = (char *)arg;
while(1)
{
memset(buf, 0, BUF_SIZE );
byte = read(socketfd, buf, BUF_SIZE); //读取server数据,有数据更新才读取,否则阻塞
if(byte == 0)
{
perror("read over");
exit(0);
}
if(byte < 0)
{
perror("read failed");
pthread_exit(0);
}
printf("len:%ld %s",strlen(buf), buf);
}
exit(0); //退出整个线程
}
int main(int argc, void *argv[] )
{
struct sockaddr_in servaddr;
signal(SIGINT, FuncExit); //接收终端ctrl+C的信号执行FuncExit函数
if( (socketfd = socket(AF_INET, SOCK_STREAM, 0) ) == -1) //socket函数类似于open函数,打开并创建一个socket,AF_INET使用IPV4协议族,SOCK_STREAM为TCP套接口
{
perror("socket create failed!");
return -1;
}
memset(&servaddr, 0, sizeof(servaddr) );
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
servaddr.sin_port = htons(8888);
if(connect(socketfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0 )
{
perror("socket connect failed!");
return -1;
}
char rdbuf[100], wdbuf[100];
pthread_t tid;
pthread_create(&tid, NULL, func, (void*)rdbuf);
while(1)
{
memset(&wdbuf, 0, sizeof(wdbuf) );
fgets(wdbuf, sizeof(wdbuf), stdin);
write(socketfd, wdbuf, sizeof(wdbuf)); //发送数据到server
}
return 0;
}
10、服务器多线程连接客服端例子:
#define MAX_LISTEN_QUE 100
#define BUF_SIZE 100
int num = 0;
int sockfd[MAX_LISTEN_QUE] = {0};
//线程退出处理函数
void FuncExit(int signal)
{
int i = 0;
for(i=0; i<=num; i++)
close(sockfd[i]);
printf("socketC exit!\n");
exit(0);
}
//服务器接收客户端数据
void *ClientToServer(void *arg)
{
int byte = 0;
char rdbuf[BUF_SIZE] = {0};
int *readfd = (int *)arg;
while(1)
{
memset(rdbuf, 0, BUF_SIZE );
byte = read(*readfd, rdbuf, BUF_SIZE); //读取client数据,有数据更新才读取,否则阻塞
if(byte == 0) //客户端关闭时,读取数据个数为0
{
printf("sockfd:%d read over\n", *readfd);
close(*readfd);
pthread_exit(0);
}
if(byte < 0)
{
perror("read failed");
close(*readfd);
pthread_exit(0);
}
printf("sockfd:%d %s",*readfd, rdbuf);
}
}
//服务器向客户端发送数据
void *ServerToClient(void *arg)
{
char wdbuf[BUF_SIZE] = {0};
int timep;
int *writefd = (int*)arg;
struct tcp_info info;
int len=sizeof(info);
while(1)
{
getsockopt(*writefd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
if((info.tcpi_state==TCP_ESTABLISHED) ) //判断客户端未断开 else 断开
{
memset(wdbuf, 0, BUF_SIZE );
timep = time(NULL);
snprintf(wdbuf, sizeof(wdbuf), "%s", ctime((time_t *)&timep));
write(*writefd, wdbuf, strlen(wdbuf));
sleep(10);
}
else
{
printf("close socket\n");
close(*writefd);
}
}
}
int main(int argc, void *argv[] )
{
int listenfd, opt = 1;
struct sockaddr_in server, client;
socklen_t len;
int ret;
signal(SIGINT, FuncExit); //接收终端ctrl+C的信号执行FuncExit函数
listenfd = socket(AF_INET, SOCK_STREAM, 0); //socket函数类似于open函数,打开并创建一个socket,AF_INET使用IPV4协议族,SOCK_STREAM为TCP套接口
if(listenfd < 0)
{
perror("Create socket fail.");
return -1;
}
if( (ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) ) < 0) //设置IP地址端口可重用
{
perror("Error, set socket reuse addr failed");
return -1;
}
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET; //IPV4协议
server.sin_port = htons(8888); //端口号
server.sin_addr.s_addr = htonl(INADDR_ANY); //自动生成自身IP做服务器IP给客户端连接
len = sizeof(struct sockaddr);
if(bind(listenfd, (struct sockaddr *)&server, len)<0) //绑定
{
perror("bind error.");
return -1;
}
listen(listenfd, MAX_LISTEN_QUE); //设置最大监听数
pthread_t tid[MAX_LISTEN_QUE];
while(1)
{
sockfd[num] = accept(listenfd, (struct sockaddr *)&client, &len); //等待客户端连接,没有则挂起睡眠
if(sockfd < 0)
{
perror("accept error.");
return -1;
}
pthread_create(&tid[num], NULL, ServerToClient, (void*)&sockfd[num]);
pthread_create(&tid[num], NULL, ClientToServer, (void*)&sockfd[num]);
num++;
printf("num:%d\n", num);
}
return 0;
}