您的位置:首页 > 理论基础 > 计算机网络

Linux下搭建一个简单的TCP通信

2016-07-26 17:16 495 查看
基础知识
socket && socket pair
socket是 IP号 + 端口号。
因此,在一个网络中,每个socket都是一个唯一的进程。

而TCP协议中,建立连接的两个进程分别用2个socket表示,这两个socket组成一对"socket pair"。

可以看出 这个 "socket pair" 即是一个唯一的连接,而他们之间通信的本质,也正是进程间通信。

大端小端:

大端 小端是内存中存放数据的两种方式,其中:
大端是 高位数据存放在低地址,低位数据存放在高地址
小端反之。
而网络数据传输采用的是大端,因此在发送数据和接收数据的时候都要进行判断。如果不统一,则需要进行转换。
至于在实际编写代码的时候,下面是现成的轮子:
uint32_t htonl(uint32_t hostlong);//将一个无符号短整形数从网络字节顺序转换为主机字节顺序
uint32_t htons(uint32_t hostshort);//将一个无符号长整形数从网络字节顺序转换为主机字节顺序
uint32_t ntohl(uint32_t netlong);//将主机的无符号短整形数转换成网络字节顺序
uint32_t ntohs(uint32_t netshort);//将主机的无符号长整形数转换成网络字节顺序


调用网络库的函数:
socket()  //打开网络端口
bind()    //将sockfd和地址绑定,并用于网络通信
listen()  //声明sockfd处于监听的状态
connect() //建立连接(也就是三次握手的步骤,当然是由客户端发出)(connect到的地址就是server中 bind()的地址)
accept()  //接受连接 (不属于三次握手,而是三次握手之后的通信)


—————————————————————
代码实现
工作流程:
Server端一直挂起,等待客户端的连接,一旦建立连接,开始通信

Server端

三种模式:
单执行流的Server端每次只能和一个Client端连接
多进程的Server能同时和多个Client连接,但开销较大
多线程的Server用多个线程的方式同时和多个Client端连接,我的Server端就采用的是这种模式

Server建立连接并进行通信主要分为4个步骤:
1、创建 socket
2、将socket 和 Server的IP、端口号进行绑定
3、设置为监听状态
4、每建立一个链接,就开启一个新的线程,在线程内部与对应的Client端进行通信

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

//server

static void usage(const char *proc )
{
printf("Usage: %s [ip] [port]\n", proc);
}

void* thread_run(void *arg)
{
printf("create a new thread\n");
int fd = (int)arg;
char buf[1024];
while(1)
{
memset(buf, '\0', sizeof(buf));
ssize_t _s = read(fd, buf, sizeof(buf) - 1 );
if( _s > 0 )
{
buf[_s] = '\0';
printf("client :# %s\n", buf);

memset(buf, '\0', sizeof(buf));
printf("please enter: ");
fflush(stdout);
_s = read(0, buf, sizeof(buf) - 1);
if(_s > 0)
{
buf[_s - 1] = '\0';
write(fd, buf, strlen(buf) );
}
}
else if(_s == 0)
{
printf("client close...\n");
break;
}
else
{
printf("read error...\n");
break;
}
}
}

int main(int argc, char *argv[] )
{
if(argc != 3 )
{
usage(argv[0]);
exit(1);
}

//1.CreateSocket
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sock < 0)
{
perror("socket");
return 2;
}

struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[2]));  //argv[2]
local.sin_addr.s_addr = inet_addr(argv[1]);

//2.Bind
if( bind(listen_sock, (struct sockaddr*)&local, sizeof(local) ) < 0)
{
perror("bind");
return 3;
}

//3.Always Listen
listen(listen_sock, 5);

//4.Accept
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
while(1)
{
int fd = accept(listen_sock, (struct sockaddr*)&peer, &len);
if(fd < 0)
{
perror("accept");
return 4;
}

printf("get a new link... socket -> %s : %d\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));

pthread_t id;
pthread_create(&id, NULL, thread_run, (void*)fd);
pthread_detach(id);

}
return 0;
}


Client端:

Client端建立连接并进行通信主要分为4个步骤:
1、创建Socket
2、连接
3、通信

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

//client

static void usage(const char *proc )
{
printf("Usage: %s [ip] [port]\n", proc);
}

int main(int argc, char *argv[])
{
if(argc != 3 )
{
usage(argv[0]);
exit(1);
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
perror("socket");
return 2;
}
struct sockaddr_in remote;
remote.sin_family = AF_INET;
remote.sin_port = htons(atoi(argv[2]));
remote.sin_addr.s_addr = inet_addr(argv[1]);

if( connect(sock, (struct sockaddr*)&remote, sizeof(remote) ) < 0)
{
perror("connect");
return 3;
}

char buf[1024];
while(1)
{
printf("Please Enter: ");
fflush(stdout);
ssize_t _s = read(0, buf, sizeof(buf) - 1);

if(_s > 0)
{
buf[_s - 1] = '\0';
write(sock, buf, strlen(buf));
_s = read(sock, buf, sizeof(buf));
if( _s > 0)
{
buf[_s] = '\0';
printf("%s\n", buf);
}
}
}
return 0;
}


—————————————————————
总结&&一些值得注意的事情
一、汇总Server 和 Client的步骤:

Server端:1、创建 socket2、将socket 和 Server的IP、端口号进行绑定3、设置为监听状态4、等待,直到收到Client端的连接请求每建立一个链接,就开启一个新的线程,在线程内部与对应的Client端进行通信
Client端:1、创建Socket2、连接3、通信

二、为什么不用void*socket编程采用sockaddr*强制类型转换的方式对其家族的类型进行传参当然相比,void*的通用性更强一些,理论上用void*设计更好,
但socket网络库要早于ANSI规范的提出,所以那时候并没有void*.......

三、Client端的socket没必要进行绑定
client端的端口号并不需要像server端那样固定以方便连接,因此不需要绑定,
而由于没有调用bind,client的端口号是由内核自动分配的
四、”Address already in use“
如下图:


建立连接,然后关闭server端,再打开,会提示这样,这是因为:server程序终止了,但TCP协议层的连接并未完全断开,当前IP被占用了。TCP协议规定,主动关闭连接的一方要处于 TIME_WAIT 状态,等待两个MSL的时间才回到CLOSE的状态。
MSL的具体时间由操作系统决定,通常 server主动关闭2分钟后 就可以再次启动了
当然,如果等不及的话,也可以用setsockopt,:
使用setsockopt,使得socket可以被重用
具体的做法为,在socket调用和bind调用之间加上一段对socket的设置:

int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
这段代码的设置,表示允许创建端口号相同但IP地址不同的多个socket描述符

五、完整的实现
https://github.com/HonestFox/NetWorks/tree/TCP
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  网络 Linux 主机