首先我们先介绍一些socket编程的基本API,利用这个API实现一个简单的C-S模型,在这个模型中,服务器接收到客户端的消息后,会将接受到的字符串进行大小写转换,然后发送给客户端,并打印。
1、socket函数--创建一个套接字
1 #include <sys/types.h> /* See NOTES */ 2 #include <sys/socket.h> 3 4 int socket(int domain, int type, int protocol);
参数分析:
domain:指定一个通信域,通常用Ipv4协议,使用 AF_INET
type:指定流的方式,tcp是基于流的传输,通常传入SOCK_STREAM,udp基于数据报的传输,传入SOCK_DGRM
protocol:这个参数是用于一个第二选择,我们传入0即可
return value:
成功的话,返回一个套接字的文件描述符,失败返回-1.
2、bind函数--将套接字绑定到地址(ip+port)上
1 #include <sys/types.h> /* See NOTES */ 2 #include <sys/socket.h> 3 4 int bind(int sockfd, const struct sockaddr *addr, 5 socklen_t addrlen);
参数分析:
sockfd:套接字文件描述符
addr:通过创建一个结构体struct sockaddr_in srvaddr,然后强转成定义的sockaddr类型即可
addrlen:前面创建套接字地址结构体的大小
返回值:
成功返回0,失败返回-1
3、listen函数--监听连接,定义最大连接数
1 #include <sys/types.h> /* See NOTES */ 2 #include <sys/socket.h> 3 4 int listen(int sockfd, int backlog);
参数分析:
sockfd:套接字文件描述符
backlog:最大连接数,SOMAXCONN,值为128
返回值:
成功返回0,失败返回-1
4、accept函数--接受一个连接
1 #include <sys/types.h> /* See NOTES */ 2 #include <sys/socket.h> 3 4 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数分析:
sockfd:套接字文件描述符
addr:通过创建一个结构体struct sockaddr_in srvaddr,然后强转成定义的sockaddr类型即可
addrlen:前面创建套接字地址结构体的大小
返回值:
成功返回一个用于通信的文件描述符,失败返回-1
5、accept函数--建立一个连接
1 #include <sys/types.h> /* See NOTES */ 2 #include <sys/socket.h> 3 4 int connect(int sockfd, const struct sockaddr *addr, 5 socklen_t addrlen);
参数分析:
sockfd:套接字文件描述符
addr:通过创建一个结构体struct sockaddr_in srvaddr,然后强转成定义的sockaddr类型即可
addrlen:前面创建套接字地址结构体的大小
返回值:
成功返回0,失败返回-1
SERVER.CPP
1 #include <iostream> 2 #include <stdio.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <stdlib.h> 6 #include <arpa/inet.h> 7 #include <netinet/in.h> 8 #include <string.h> 9 #include <unistd.h> 10 using namespace std; 11 12 13 #define ERR_EXIT(m) 14 do 15 { 16 perror(m); 17 exit(EXIT_FAILURE); 18 }while(0); 19 20 int main(int argc, char* argv[]){ 21 if(argc < 2){ 22 cout << "please add port;" <<endl; 23 exit(-1); 24 } 25 int port = atoi(argv[1]); 26 //创建一个套接字 27 int srvfd = socket(AF_INET, SOCK_STREAM, 0); 28 if(srvfd == -1){ 29 ERR_EXIT("socket"); 30 } 31 //定义一个结构体并填充 32 struct sockaddr_in srvaddr; 33 srvaddr.sin_family = AF_INET; 34 srvaddr.sin_port = htons(port); 35 srvaddr.sin_addr.s_addr = INADDR_ANY; 36 //将套接字绑定到地址上 37 int ret = bind(srvfd, (const struct sockaddr*)&srvaddr, sizeof(srvaddr)); 38 if(ret == -1){ 39 ERR_EXIT("bind"); 40 } 41 //监听套接字 42 ret = listen(srvfd, SOMAXCONN); 43 if(ret == -1){ 44 ERR_EXIT("listen"); 45 } 46 47 struct sockaddr_in sockaddr; 48 socklen_t socklen = sizeof(sockaddr); 49 //定义一个套接字,通常为已经连接的套接字 50 int conn; 51 conn = accept(srvfd, (struct sockaddr*)&sockaddr, &socklen); 52 if(conn == -1){ 53 ERR_EXIT("conn"); 54 } 55 else{ 56 cout << "peer ip addr = " << inet_ntoa(sockaddr.sin_addr) << ", port = " << htons(sockaddr.sin_port) << endl; 57 } 58 //循环获取数据,发送数据 59 char recvbuf[1024] = {0}; 60 while(1){ 61 memset(recvbuf, 0, 1024); 62 int ret = read(conn, recvbuf, 1024); 63 fputs(recvbuf, stdout); 64 for(int i = 0; i < strlen(recvbuf); i++){ 65 if(recvbuf[i] >= 'a' && recvbuf[i] <= 'z'){ 66 recvbuf[i] -= 32; 67 } 68 else if(recvbuf[i] >= 'A' && recvbuf[i] <= 'Z'){ 69 recvbuf[i] += 32; 70 } 71 } 72 write(conn, recvbuf, strlen(recvbuf)); 73 } 74 //关闭套接字 75 close(srvfd); 76 close(conn); 77 return 0; 78 }
CLIENT.CPP
1 #include <iostream> 2 #include <sys/types.h> 3 #include <sys/socket.h> 4 #include <stdio.h> 5 #include <unistd.h> 6 #include <netinet/in.h> 7 #include <arpa/inet.h> 8 #include <stdlib.h> 9 #include <string.h> 10 using namespace std; 11 12 #define ERR_EXIT(m) 13 do 14 { 15 perror(m); 16 exit(EXIT_FAILURE); 17 }while(0); 18 19 int main(int argc, char *argv[]) 20 { 21 if(argc < 2){ 22 ERR_EXIT("need port"); 23 } 24 int port = atoi(argv[1]); 25 //创建一个套接字 26 int cltfd = socket(AF_INET, SOCK_STREAM, 0); 27 if(cltfd == -1){ 28 ERR_EXIT("socket"); 29 } 30 //定义一个地址结构 31 struct sockaddr_in cltaddr; 32 cltaddr.sin_family = AF_INET; 33 cltaddr.sin_port = htons(port); 34 cltaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 35 //进行连接 36 int ret = connect(cltfd, (const struct sockaddr*)&cltaddr, sizeof(cltaddr)); 37 if(ret == -1){ 38 ERR_EXIT("connect"); 39 } 40 else{ 41 cout << "connect success" << endl; 42 } 43 char sendbuf[1024] = {0}; 44 char recvbuf[1024] = {0}; 45 //从标准输入中读入 46 while(fgets(sendbuf, 1024, stdin) != NULL){ 47 write(cltfd, sendbuf, strlen(sendbuf)); 48 if(read(cltfd, recvbuf, 1024) > 0){ 49 cout << "get info from server" << endl; 50 fputs(recvbuf, stdout); 51 } 52 memset(sendbuf, 0, 1024); 53 memset(recvbuf, 0, 1024); 54 } 55 close(cltfd); 56 return 0; 57 }
补充:
当我们使用ctrl+c结束进程后,服务器是无法立即重启的,如果重启服务器会提示---bind: Address already in use
原因就在于服务器重新启动时需要绑定地址,而这个时候网络正处于TIME_WAIT状态,只有这个状态退出后,该地址才能被重新绑定。TIME_WAIT的时间是两个MSL,大约是1~4分钟。若每次服务器重启都需要等待TIME_WAIT结束那就太不合理了,好在选项SO_REUSEADDR能够解决这个问题。
服务器端尽可能使用REUSEADD,在bind()之前调用setsockopt来设置SO_REUSEADDR套接字选项,使用SO_REUSEADDR选项可以使不必等待TIME_WAIT状态消失就可以重启服务器。
1 /*设置地址重复使用*/ 2 int on = 1; //on为1表示开启 3 if(setsockopt(listenfp ,SOL_SOCKET,SO_REUSEADDR,&on,sieof(on))<0) 4 ERR_EXIT("setsockopt error");