(一)TCP编程流程
服务器端编程流程
/*
第一步:创建socket int socket(int domain.int type,int protocol)
成功时返回一个socket 失败时返回-1并设置error
domain:告诉系统使用哪个底层协议族 PF_INET(ipv4) PF_INET6(ipv6)
type:指定服务类型 SOCK_STREAM(流服务) SOCK_UGRAM(数据报服务)
protocol:在前两个协议的结合下,在选择一个具体的协议,默认值为0,表示使用默认协议
第二步:命名socket int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen)
将socket与地址协议族中的某个socket地址绑定 成功时返回0,失败返回-1并设置error
常见的error 1.EACCES 被绑定的地址是受保护的地址,仅可以被超级用户访问;普通用户绑定0-1023的端口号时会返回EACCES错误
2.EADDRINUSE 被绑定的地址正在被使用 比如将socket绑定到正处于time_wait状态的地址上
第三步:监听socket int listen(int sockfd,int backlog)
sockfd被指定要监听的socket backlog表示内核监听队列的最大长度
Linux内核版本2.2之前是处于SYN_RECV和ESTABLISHED的socket的上限
2.2版本之后表示处于ESTABLISHED的socket的上限,
处于半连接状态的socket上限由/proc/sys/net/ipv4/tacp_max_syn_backlog内核参数设置
超过此上限,服务器将不再接受新的客户端连接并返回ECONNREFUSED错误信息
第四步:接受连接 int accept(int socket,struct sockaddr *addr,socklen_t *addrlen)
sockfd指的是经过listen系统调用的监听socket addr参数用来指定被接受连接的socket地址 addrlen为其长度
accept成功时返回一个新的连接socket,该socket唯一标识了这个被接受的连接 失败时返回-1并设置error
第五步:recv ssize_t recv(int sockfd,void* buf,siez_t len ,int flags)
返回实际读取到的数据的长度
第六步:send ssize_t send(int sockfd,const void* buf,size_t len,int flags)
返回实际写入的数据的长度
第七步:close int close(int fd)
fd的等待关闭连接的socket close系统调用并不是立即关闭 而是将fd的引用计数为0时才真正关闭连接
多进程程序中,一次fork系统调用默认父进程打开得socket的引用计数加1,所以要将父子进程中的socket都关闭才是真正断开连接
如果想立即终止连接 可以使用下列的函数
int shutdown(int fd,int howto)
howto决定了关闭的行为
SHUT_RD 关闭socket读的一端 应用程序不能针对socket进行读操作 并且该socket接受缓冲区中的数据都被丢弃
SHUT_WR 关闭socket写的一端 在关闭之前将socket发送缓冲区的数据全部发送出去 应用程序不能针对socket进行写操作 这种情况下 经常处于半关闭状态
SHUT_RDWR 同时关闭socket上的读与写
*/
客户端编程流程
/*
第一步:创建socket int socket(int domain.int type,int protocol)
成功时返回一个socket 失败时返回-1并设置error
domain:告诉系统使用哪个底层协议族 PF_INET(ipv4) PF_INET6(ipv6)
type:指定服务类型 SOCK_STREAM(流服务) SOCK_UGRAM(数据报服务)
protocol:在前两个协议的结合下,在选择一个具体的协议,默认值为0,表示使用默认协议
第二步:命名socket int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen)
将socket与地址协议族中的某个socket地址绑定 成功时返回0,失败返回-1并设置error
常见的error 1.EACCES 被绑定的地址是受保护的地址,仅可以被超级用户访问;普通用户绑定0-1023的端口号时会返回EACCES错误
2.EADDRINUSE 被绑定的地址正在被使用 比如将socket绑定到正处于time_wait状态的地址上
第三步:连接 int connect(int sockfd,const struct sockaddr* ser_addr,socklen_t addrlen)
sockfd是系统socket系统调用产生的socketfd ser_addr是服务器监听的socket地址 addrlen为此地址的长度
成功时返回0 一旦连接成功 sockfd就唯一标识了这个连接
失败时返回-1并设置error
ECONNREFUSED 目标端口不存在 连接被拒绝
ETIMEDOUT 连接超时
与服务器端相同
第四步:send
第五步:recv
第六步:close
*/以下为代码示例
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/select.h>
void main()
{
int listen_fd = socket(AF_INET,SOCK_STREAM,0);//创建套接字
assert(listen_fd != -1);
struct sockaddr_in ser,cli;//在绑定函数中需要的结构体,用来记录客户端的iip地址和端口号
ser.sin_family = PF_INET;//地址族:TCP/IP
ser.sin_port = htons(6000);//将客户端端口号转化为网络字节序
ser.sin_addr.s_addr = inet_addr("127.0.0.1");//将客户端的ip地址转化为网络字节序;注意这里输入的ip地址是链接本机;
int ret = bind(listen_fd,(struct sockaddr*)&ser,sizeof(ser));//命名套接字
assert(ret != -1);
listen(listen_fd,5);//监听套接字
printf("listen finish\n");
while(1)
{
int len = sizeof(cli);
//连接套接字 通过c实现服务器与客户端之间的通信
int c = accept(listen_fd,(struct sockaddr*)&cli,&len);
assert(c != -1);
char buff[128] = {0};
//接受客户端消息
int n = recv(fds[i],buff,128,0);
if(n <= 0)
{
close(fds[i]);
}
printf("buff::%s\n",buff);
//向客户端发消息
send(fds[i],"0k",2,0);
}
}
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
void main()
{
int sock_fd = socket(AF_INET,SOCK_STREAM,0);
assert(sock_fd != -1);
struct sockaddr_in ser;
ser.sin_family = PF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sock_fd,(struct sockaddr*)&ser,sizeof(ser));
assert(ret != -1);
while(1)
{
char buff[128] = {0};
printf("please input:");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3) == 0)
{
break;
}
send(sock_fd,buff,strlen(buff) - 1,0);//不将 \n发送给服务器
memset(buff,0,128);
recv(sock_fd,buff,127,0);
printf("buff:: %s\n",buff);
}
close(sock_fd);
}
注意在网络中传输的数据的字节序都是大端模式,在PC端存储的数据都是小端模式,所以在网络通讯中必须将小端模式转为大端模式,所以在上述代码编程中用到了转换字节序的函数
1.转换端口号
#include <netinet/in.h>
unsigned short int htons(unsigned short int hostshort);
2.转换ip地址
#include <arpa/inet.h>
int_addr_t inet_addr(const char *strptr)
(二)UDP编程流程
服务器端编程流程
创建socket(socket)------->命名socket(bind)------>recvfrom()----->sendto()------>close()
客户端编程流程
创建socket(socket)------->命名socket(bind)------>连接服务器connect()---->recvfrom()----->sendto()------>close()