TCP套接字编程

TCP套接字编程

在实现一个主机上的不同进程相互之间通信,可以用管道、信号量或者共享内存来实现。如果要实现网络中不同进程间的通信就要使用套接字编程来实现。一个套接字就是一个IP地址和这个IP地址上的唯一端口号。

server
1.#include <stdio.h>
2.#include <sys/types.h>
3.#include <sys/socket.h>
4.#include <stdlib.h>
5.#include <string.h>
6.#include <arpa/inet.h>
7.#include <netinet/in.h>
8.
9.
10.static void usage(const char *str)//从命令行参数中直接读取所需的IP地址和端口号
11.
{
12. printf("usage:%s [loacl_addr] [local_port]", str);
13.}
14.
15.
16.int main(int argc, char *argv[])
17.
{
18.
19. if(argc < 3){
20. usage(argv[0]);
21.
22. exit(1);
23. }
24.
25. int listen_sock = socket(AF_INET, SOCK_STREAM, 0);//socket的返回值是一个文件描述符
26. if(listen_sock < 0){
27. perror("socket");
28. return 1;
29. }
30.
31. printf("sock: %d\n", listen_sock);
32. //sockaddr_in 里既有IP地址,又有端口号
33. struct sockaddr_in local;//定义一个sockaddr_in 接口体 grep -ER 'struct sockaddr_in {' /usr/include/
34. local.sin_family = AF_INET;
35. local.sin_port = htons(atoi(argv[2]));//要将主机的端口号转换为网络中的端口号
36. local.sin_addr.s_addr = inet_addr(argv[1]);//将字符串的IP地址转换为4字节的IP地址
37.
38.
39. if(bind(listen_sock,(struct sockaddr*)&local/*强转为struct sockaddr*类型 */, sizeof(local))<0){//绑定IP地址和端口号
40. perror("bind");
41. return 2;
42. }
43.
44. if(listen(listen_sock, 10) < 0){//监听有没有client连接server
45. perror("listen");
46. return 3;
47. }
48.
49. while(1){
50. struct sockaddr_in peer;
51. socklen_t len = sizeof(peer);
52. int new_sock = accept(listen_sock,(struct sockaddr*)&peer, &len); //accept的返回值是返回一个新的文件描述符
53. if(new_sock < 0){
54. perror("accept");
55. continue;
56. }
57.
58. printf("get new client [%s:%d]\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));//获取client的IP地址和端口号
59.
60. //version1
61. char buf[1024];
62. while(1){
63. ssize_t s = read(new_sock, buf, sizeof(buf)-1);//有BUG,只能用于测试
64. if(s > 0){
65. buf[s] = 0;
66. printf("client# %s\n", buf);//buf就是client端往里写的
67. write(new_sock, buf, strlen(buf));//TCP是全双工的,所以把buf里的数据写进new_sock里
68. }else if(s == 0){//若读到0说明对端client已经断开连接了
69. printf("quit\n");
70. return 4;
71. }else{//出错返回
72. perror("read");
73. return 5;
74. }
75. }
76. }
77. close(listen_sock);//在server退出的时候关闭监听
78. return 0;
79.}

这就是一个简单的tcp套接字实现,在写好服务后还要让client来连接服务器,所以就还要编写client端,或者直接使用telnet(远程登录工具)来登录到服务器上。

client
1.#include <stdio.h>
2.#include <sys/types.h>
3.#include <sys/socket.h>
4.#include <stdlib.h>
5.#include <string.h>
6.#include <arpa/inet.h>
7.#include <netinet/in.h>
8.
9.static void usage(char *str)
10.
{
11. printf("Usage: %s [server_ip] [server_port]\n", str);//这里就是对端(server端的ip地址和端口号了)
12.}
13.
14.
15.int main(int argc, char *argv[])
16.
{
17. if(argc != 3){
18. usage(argv[0]);
19. return 1;
20. }
21.
22. int sock = socket(AF_INET, SOCK_STREAM, 0);
23. if(sock < 0){
24. perror("socket");
25. return 2;
26. }
27.
28. struct sockaddr_in remote;
29. remote.sin_family = AF_INET;
30. remote.sin_port = htons(atoi(argv[2]));
31. remote.sin_addr.s_addr = inet_addr(argv[1]);
32.
33. if(connect(sock, (struct sockaddr*)&remote,sizeof(remote))<0){//由于这里是client不用绑定IP地址和端口号(实际上没有绑定的需要),这里只需要连接到服务器上即可,connect返回的是对端的ip地址
34. perror("connect");
35. return 3;
36. }
37.
38. while(1)
39. {
40. char buf[1024];
41. printf("please Enter# ");
42. fflush(stdout);
43. ssize_t s = read(0, buf, sizeof(buf)-1);//client 从标准输入里读到的数据放在buf中
44. if(s > 0){
45. buf[s-1] = 0;//将换行符和置为0
46. write(sock, buf, strlen(buf));//和server一样,TCP为全双工
47. ssize_t _s = read(sock, buf, sizeof(buf)-1);
48. if(_s > 0){
49. buf[_s] = 0;
50. printf("server echo# %s\n", buf);
51. }
52. }
53. }
54. return 0;
55.}
56.

将server和client都写好之后就进行一下通行测试 
先用client端连接server 并发消息给server

测试结果


 
server端收到,并回显给client端 
用telnet工具进行远程登录一下


 
在使用telnet工具登录的时候,若找到目标主机上之后输入”Ctrl+]”进行连接,断开时也是先输入“Ctrl+]”,然后在“telnet>”后面输入quit断开连接

多进程TCP套接字编程

相对于普通TCP套接字,多进程TCP服务器具有良好的稳定性

server
1.#include <stdio.h>
2.#include <sys/types.h>
3.#include <sys/socket.h>
4.#include <stdlib.h>
5.#include <string.h>
6.#include <arpa/inet.h>
7.#include <netinet/in.h>
8.
9.
10.static void usage(const char *str)//从命令行参数中直接读取所需的IP地址和端口号
11.
{
12. printf("usage:%s [loacl_addr] [local_port]\n", str);
13.}
14.
15.
16.int main(int argc, char *argv[])
17.
{
18.
19. if(argc < 3){
20. usage(argv[0]);
21.
22. exit(1);
23. }
24.
25. int listen_sock = socket(AF_INET, SOCK_STREAM, 0);//socket的返回值是一个文件描述符
26. if(listen_sock < 0){
27. perror("socket");
28. return 1;
29. }
30.
31. printf("sock: %d\n", listen_sock);
32. //sockaddr_in 里既有IP地址,又有端口号
33. struct sockaddr_in local;//定义一个sockaddr_in 接口体 grep -ER 'struct sockaddr_in {' /usr/include/
34. local.sin_family = AF_INET;
35. local.sin_port = htons(atoi(argv[2]));//要将主机的端口号转换为网络中的端口号
36. local.sin_addr.s_addr = inet_addr(argv[1]);//将字符串的IP地址转换为4字节的IP地址
37.
38.
39. if(bind(listen_sock,(struct sockaddr*)&local/*强转为struct sockaddr*类型 */, sizeof(local))<0){//绑定IP地址和端口号
40. perror("bind");
41. return 2;
42. }
43.
44. if(listen(listen_sock, 10) < 0){//监听有没有client连接server
45. perror("listen");
46. return 3;
47. }
48.
49. while(1){
50. struct sockaddr_in peer;
51. socklen_t len = sizeof(peer);
52. int new_sock = accept(listen_sock,(struct sockaddr*)&peer, &len); //accept的返回值是返回一个新的文件描述符
53. if(new_sock < 0){
54. perror("accept");
55. continue;
56. }
57.
58. printf("get new client [%s:%d]\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));
59.
60. //version2
61. pid_t id = fork();
62. if(id < 0){
63. perror("fork");
64. close(new_sock);
65. }else if(id == 0){
66. close("listen_sock");
67. if(fork() > 0){
68. exit(0);
69. }
70. while(1)
71. {
72. char buf[1024];
73. ssize_t s = read(new_sock, buf, sizeof(buf)-1);
74. if(s > 0){
75. buf[s] = 0;
76. printf("client# %s\n", buf);
77. write(new_sock, buf, strlen(buf));
78. }else if(s==0){
79. printf("client quit...\n");
80. break;
81. }
82. }
83. close(new_sock);
84. }else{
85. close(new_sock);
86. }
87. }
88.return 0;
89.}

client端只是连接server的,所以多进程多线程的client端都和普通版本的client一样

测试结果


 
注意,因为socket返回的是一个新的没有被占用文件描述符,所以打印出来的sock都是3

多线程TCP套接字

我们知道线程相对于进程的优点就是程序的并发执行,所以系统的性能会进一步提升。

1.#include <stdio.h>
2.#include <sys/types.h>
3.#include <pthread.h>
4.#include <stdlib.h>
5.#include <sys/socket.h>
6.#include <netinet/in.h>
7.#include <unistd.h>
8.#include <arpa/inet.h>
9.#include <string.h>
10.#include <fcntl.h>
11.
12.static void usage(const char *proc)
13.
{
14. printf("usage: %s [local_IP] [local_port]\n", proc);
15.}
16.
17.int startup(const char *ip, int port)
18.
{
19. int sock = socket(AF_INET, SOCK_STREAM, 0);
20. if(sock < 0){
21. perror("sock");
22. exit(1);
23. }
24.
25. printf("sock: %d\n", sock);
26.
27. struct sockaddr_in local;
28. local.sin_family = AF_INET;
29. local.sin_port = htons(port);
30. local.sin_addr.s_addr = inet_addr(ip);
31.
32. if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){
33. perror("bind");
34. exit(2);
35. }
36.
37. if(listen(sock, 10) < 0){
38. perror("listen");
39. exit(3);
40. }
41.
42. return sock;
43.}
44.
45.void *request(void *arg)
46.
{
47. int new_sock = (int)arg;
48. while(1)
49. {
50. char buf[1024];
51. ssize_t s = read(new_sock, buf, sizeof(buf));
52. if(s > 0)
53. {
54. buf[s] = 0;
55. write(new_sock, buf, strlen(buf));
56. printf("get a client# %s\n", buf);
57. }else if(s == 0){
58. printf("client quit...\n");
59. break;
60. }else{
61. perror("read");
62. break;
63. }
64. }
65. close(new_sock);
66. return (void*)0;
67.}
68.
69.int main(int argc, char *argv[])
70.
{
71. if(argc != 3)
72. {
73. usage(argv[0]);
74. return 4;
75. }
76.
77. int listen_sock = startup(argv[1],atoi(argv[2]));
78.
79. while(1)
80. {
81. struct sockaddr_in client;
82. socklen_t len = sizeof(client);
83. int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
84. if(new_sock < 0){
85. perror("new_sock");
86. continue;
87. }
88.
89. printf("get new client [%s:%d]\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
90.
91. pthread_t id;
92. pthread_create(&id, NULL, request, (void*)new_sock);
93. pthread_detach(id);//将线程分离,运行完成之后自动释放所有资源
94. // close(new_sock);
95. }
96. return 0;
97.}
测试结果




如果client端退出,server 端会提示client已退出,对服务器没什么影响,client可以重新再连接。可是当服务器直接退出之后,想再一次重新启动服务,会出现绑定失败的提示


这是因为,TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL的时间后才能回到CLOSED状态,因为先终止的是server端,所以server会处于TIME_WAIT状态,等待一段时间后就会重新回到CLOSED 状态,而等待的时间在RFC1122中规定为两分钟,可因系统的不同得到不同的MSL,Linux下为半分钟左右。 
如果,在server主动断开连接后,不想等待,直接可以再次启动服务器,可以在创建套接字和绑定套接字之间加上两行代码表示允许创建端口号相同但IP地址不同的多个socket描述符

1.int opt = 1;
2.setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
测试结果


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值