服务端流程
- 调用socket函数获取一个套接字文件描述符
int listenfd = socket(AF_INET,SOCK_STREAM,0);
第一个参数就是表示协议族,采用TCP/IP协议族进行通信。SOCK_STREAM指的是用TCP通信,字节流通信
2. 给地址结构sockaddr_in serverAddr赋值
struct sockaddr_in serverAddr;
serverAddr.sin_family = PF_INET;
serverAddr.sin_port = htons(SERVER_PORT);
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
- 绑定地址结构调用bind函数
bind(listener, (struct sockaddr *) &serverAddr, sizeof(serverAddr))
注意第二个参数是一个指针类型,所以需要对第二步设定好的地址结构进行取地址操作,然后再对地址进行强转,转换成sockaddr*的指针结构。第三个参数就是地址结构的字节大小,直接进行sizeof求值即可。
4. 调用listen函数,设定监听的最大上限,但是其实这并不是真正的监听。
listen(listener, 20);
这里设定的同时监听的最大上限是20个,说明的是已经建立的连接和正在建立连接的连接队列(也就是处于syn_recv状态的半连接队列的个数)加上还可以同时监听的个数总共不超过20个。
5. 调用accept函数进行真正意义上的监听
struct sockaddr_in client_address;
socklen_t client_addrLength = sizeof(struct sockaddr_in);
int clientfd = accept(listener, (struct sockaddr *) &client_address, &client_addrLength);
调用了accept函数后在三次握手中相当于服务端处于listen状态
只有当客户端调用了connect后才进行三次握手进行通信。
这里需要传出一个客户端的地址结构,所以在调用accept函数前先声明一个sockaddr_in的地址结构 ,取地址强转作为accept函数的第二个参数,第三个参数是地址结构大小socklen_t的值的地址,说明这个地址结构是传出类型的。
上边调用的那个bind函数第三个参数不是指针类型,是一个整型,说明是传入的地址结构。
6. 然后就是读写操作了
发送信息操作
char message[1024];
bzero(message, 1024);
sprintf(message, "hello world", clientfd);
int ret = send(clientfd, message, BUF_SIZE, 0);
接收信息操作
int len = recv(clientfd, buf, BUF_SIZE, 0);
- 最后关闭listenfd和clientfd
客户端流程
- 第一步同样是进行调用socket函数
int clientfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
这里面参数前面已经进行了介绍,最后一个其实也是一个宏定义,就是0,使用的是IP协议
2. 声明地址结构,设置服务端ip地址和端口号
// 填充sockadd结构,指定ip与端口
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(SERVER_PORT);
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP); // 还有一个函数可以用htonl()
这里就是一个设置ip和端口的过程,一定要和服务端主机的ip地址及端口号对应
3. 然后就是调用connect函数
connect(clientfd, (struct sockaddr *) &serverAddr, sizeof(serverAddr))
调用connect函数连接服务器。同样地地址结构是传入的,所以最后一个参数不是指针类型
4. 然后就是常规的读写操作了,和服务端的第6步一样
5. 最后关闭clientfd.
epoll系列函数的使用方法
- 在内核中创建一个红黑树文件描述符epoll_create
int epfd = epoll_create(2048);
- 往内核中添加事件epoll_ctl
struct epoll_event ev;
ev.data.fd = fd; //fd就是一个套接字描述符
ev.events = EPOLLIN;
ev.events = EPOLLIN | EPOLLET ; //设置边缘触发模式
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
该函数用于把握某个epoll文件描述符上的事务,可以注册事务,批改事务,删除事务。
参数:
epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操纵例如注册事务,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除
fd:接洽关系的文件描述符;
event:指向epoll_event的指针;
若是调用成功返回0,不成功返回-1
3. epoll_wait函数
该函数用于轮询I/O事务的产生
static struct epoll_event events[EPOLL_SIZE];
//epoll_events_count表示就绪事件的数目
int epoll_events_count = epoll_wait(epfd, events, 2048, -1);
函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
该函数用于轮询I/O事务的产生;
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理惩罚事务的数组;
maxevents:每次能处理惩罚的事务数;
timeout:守候I/O事务产生的超时价(单位我也不太清楚);-1相当于梗阻,0相当于非梗阻。一般用-1即可